vue2.x 是如何实现响应式系统的
当你把一个普通的 js 对象传入 vue 实例作为 data 选项,vue 将遍历此对象的所有prototype(属性),并使用 object.defineproperty(),将这些 prototype(属性),全部转换为 getter / setter,在 getter 中收集数据依赖,在 setter 中监听数据变化,一旦数据发生改变,在通知订阅者。
每个组件实例,都对应一个 watcher 实例,它会在组件渲染的过程把 “接触” 过的数据 prototype(属性)记录为依赖,之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染;
defineproperty 的痛点
- 它无法发现对象新增和被删除的属性,当你给一个对象添加一个新的属性,这个新增的属性没有添加到 vue 的数据更新侦查机制里;
vue.set() 可以让 vue 知道你新增了一个属性,其实 vue.set 可以让 vue 知道新增了一个属性。其实 set() 内部也是通过调用 defineproperty() 来实现;
- 当你利用索引直接设置一个数组(new array(4))或者修改数组的长度时,vue 不能检测到数组的变动;
- 当对象嵌套的层数特别深(多层嵌套)的时候,递归遍历带来的性能开销就会比较大;
object.defineproperty 代码的使用
mounted() {
// 先定义好一套规则
class observer {
constructor(data) {
for (let key of object.keys(data)) {
if (typeof data[key] === "object") {
data[key] = new observer(data[key]);
}
object.defineproperty(this, key, {
enumerable: true,
configurable: true,
get() {
console.log("you visited" + key);
return data[key];
},
set(newvalue) {
console.log("you set" + key);
console.log("new value" + newvalue);
if (newvalue === data[key]) {
return;
}
data[key] = newvalue;
},
});
}
}
}
let obj = {
name: "app",
age: 18,
a: {
b: 1,
c: {
d: 1,
},
},
};
let app = new observer(obj);
console.log(app);
app.age = 20;
app.newproperty = "new attrs";
console.log(app);
},结果:

proxy 方法的理解
proxy 在 vue3.0 中上位
可以解决 defineproperty 的痛点,因为本质的原因在于 proxy 是内置了拦截器对象。所有的外部访问都得先经过这一层拦截,不管是先前定义好的,还是新增的属性,又或者是深层的嵌套属性,访问时都会被拦截;
reflect.set()方法用于设置对象属性的值,1:目标对象:2:改变参数的名称:3:改变参数的值:4:值是this如果遇到设置器,将提供给目标调用。
此方法返回一个布尔值,该值指示该属性是否已成功设置。
proxy 代码的使用
mounted() {
const obj = {
name: "app",
age: 19,
a: {
b: 1,
c: 2,
},
};
const p = new proxy(obj, {
get(target, propkey, receiver) {
console.log("your visited:" + propkey);
// reflect.set()方法用于设置对象属性的值:1:目标对象:2:改变参数的名称:3:改变参数的值
// 此方法返回一个布尔值,该值指示该属性是否已成功设置。
return reflect.set(target, propkey, receiver);
},
set(target, propkey, value, receiver) {
console.log("you set:" + propkey);
console.log("new value:" + value);
// reflect.set()方法用于设置对象属性的值,1:目标对象:2:改变参数的名称:3:改变参数的值:4:值是this如果遇到设置器,将提供给目标调用。
// 此方法返回一个布尔值,该值指示该属性是否已成功设置。
return reflect.set(target, propkey, value, receiver);
},
});
p.age = "20";
console.log(p);
p.newproperty = "new attribute";
console.log(p);
},结果:

总结
- proxy 是用来操作对象并且扩展对象能到,而 object.defineproperty() 只是单纯的操作对象的属性;
- vue2.x 是用 object.defineproperty() 实现数据响应的,但是受限于 defineproperty() 的实现,必须递归遍历至对象的最底层;
- vue3.0 用 proxy 来拦截对象,不管对目标执行任何操作,都会先通过 proxy 的处理逻辑;
- 除了 vue3.0,还有其他的库也在使用 proxy。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论