Skip to content

响应式原理

字数: 0 字 时长: 0 分钟

简介

响应式是 Vue 的核心:数据变了,视图自动更新。Vue2 使用 Object.defineProperty 实现,Vue3 则改用 Proxy 重写了整个响应式系统。这篇笔记记录 Vue3 响应式的实现原理和 refreactive 的使用区别,面试必考。

Vue2 与 Vue3 的实现区别

  • Vue2:通过 Object.defineProperty 劫持对象每个属性的 getter / setter。缺点是:
    • 无法检测对象属性的新增和删除(需要用 Vue.set / Vue.delete)。
    • 无法直接检测数组下标修改和 length 变化(所以重写了数组的 7 个方法)。
    • 初始化时需要递归遍历所有属性,对象层级深时性能差。
  • Vue3:通过 Proxy 代理整个对象,配合 Reflect 操作源对象。优点是:
    • 可以监听属性的新增、删除。
    • 可以监听数组下标和 length 的变化。
    • 惰性劫持,访问到某一层才递归代理,性能更好。

Proxy 的基本用法

js
const obj = { name: "卡比", age: 18 };

const proxy = new Proxy(obj, {
  get(target, key) {
    console.log(`读取了 ${key}`);
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    console.log(`设置了 ${key} = ${value}`);
    return Reflect.set(target, key, value);
  },
});

proxy.name; // 读取了 name
proxy.age = 20; // 设置了 age = 20

响应式的核心流程

Vue3 的响应式可以概括为三个角色:

  1. reactive:用 Proxy 包装数据,在 get 中收集依赖(track),在 set 中触发更新(trigger)。
  2. effect:依赖收集的载体,组件的渲染、computed、watch 本质上都是 effect。
  3. 依赖关系:每个响应式属性维护一个 dep(依赖集合),记录哪些 effect 用到了它。
js
// 一个极简版的响应式实现思路
let activeEffect = null;

function effect(fn) {
  activeEffect = fn;
  fn(); // 执行一次,触发 get 收集依赖
  activeEffect = null;
}

function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      track(obj, key); // 收集依赖:把 activeEffect 存起来
      return Reflect.get(obj, key);
    },
    set(obj, key, value) {
      Reflect.set(obj, key, value);
      trigger(obj, key); // 触发更新:执行收集到的 effect
      return true;
    },
  });
}

注意

Proxy 是 ES6 新增的语法,无法被 polyfill,这也是 Vue3 不支持 IE11 的根本原因。

ref 和 reactive 的区别

对比项refreactive
支持类型任意类型(基本类型 + 对象)只支持对象类型
访问方式需要通过 .value直接访问
模板中使用自动解包,不需要 .value直接使用
解构配合 toRefs 保持响应式直接解构会丢失响应式
vue
<script setup>
import { ref, reactive } from "vue";

const count = ref(0); // 基本类型用 ref
const state = reactive({ name: "卡比" }); // 对象用 reactive

count.value++; // js 中访问 ref 要加 .value
state.name = "卡比兽";
</script>

<template>
  <!-- 模板中 ref 自动解包 -->
  <div>{{ count }}</div>
</template>

reactive 的两个坑

  1. 不能直接替换整个对象,会丢失响应式:
js
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // ❌ 旧对象的响应式连接断了
state.count = 1; // ✅ 正确姿势:修改属性
  1. 解构会丢失响应式,需要用 toRefs
js
const state = reactive({ name: "卡比", age: 18 });
const { name } = state; // ❌ name 不再是响应式
const { name } = toRefs(state); // ✅ 保持响应式
最后更新于: 2026/8/5 15:00:00