Skip to main content

数据响应式

响应式 = 数据变化时自动触发副作用(视图更新)。核心是「劫持数据的读写」,读时收集依赖,写时通知更新。

Vue2:Object.defineProperty

function observe(obj) {
if (obj === null || typeof obj !== "object") return;
Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]));
}

function defineReactive(obj, key, val) {
observe(val); // 递归处理嵌套对象
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}`); // 依赖收集
return val;
},
set(newVal) {
if (newVal === val) return;
console.log(`更新 ${key} = ${newVal}`); // 派发更新
val = newVal;
observe(newVal); // 新值若是对象也要响应式
},
});
}

const data = { name: "a", info: { age: 1 } };
observe(data);
data.name = "b"; // 更新 name = b
data.info.age = 2; // 更新 age = 2(嵌套也生效)

局限

  • 无法检测对象属性的新增/删除(Vue2 需 Vue.set/Vue.delete
  • 无法监听数组下标修改和 length(Vue2 通过重写 7 个数组方法实现)

Vue3:Proxy + Reflect

function reactive(obj) {
if (obj === null || typeof obj !== "object") return obj;

return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
console.log(`读取 ${String(key)}`); // track 依赖收集
// 惰性递归:访问到嵌套对象时才代理,比 Vue2 的递归更高效
return typeof res === "object" && res !== null ? reactive(res) : res;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
console.log(`更新 ${String(key)} = ${value}`); // trigger 派发更新
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log(`删除 ${String(key)}`);
return result;
},
});
}

优势:能直接监听属性新增/删除、数组操作,且嵌套对象惰性代理(用到才递归),性能更好。

defineProperty vs Proxy

-Object.definePropertyProxy
监听粒度针对每个 key针对整个对象
新增/删除属性监听不到可监听
数组下标/length监听不到(需 hack)可监听
嵌套对象初始化时递归全部访问时惰性递归
兼容性兼容 IE不支持 IE

参考