八股文说,vue2使用Object.definedPropoty,而Vue3使用的是Proxy。这很难理解,于是我深入调查一番原理层面,如下纯手写,非AI。
如果我是Vue开发者,想要实现“双向数据绑定”的需求,也就是对象obj中的数据,被读取或者被更新之后,我都要想办法收到通知,并且能够做一些其他的操作,比如触发页面更新等操作。
const const obj = {
a: 1,
b: 2,
c: {
d: 3,
e: 4,
},
};
obj.a // 我并不知道“读取”操作了
obj.a = 1 // 我并不知道“更新”操作了
上面的操作,我没法收到通知,做相关的处理,那怎么办呢?
我要是能重新修改obj对象的set、get方法就好了,这样“读取”会默认走get方法,“更新”会默认走set方法。
Object.definedPropoty就可以做到,实现代码如下:
// vue2
function observer(dataSource: { [key: string]: any }) {
for (let key in dataSource) { // [注释1]
const value = dataSource[key];
if(typeof value === 'object' && value !== null) {
observer(value); // [注释2]
}
const listener = Object.defineProperty(dataSource, key, {
set: (sth) => {
if(key === sth) return;
console.log('修改更新', key);
key = sth;
},
get: () => {
console.log('读取', key);
return value;
},
});
listener;
}
return dataSource;
}
const listener = observer(obj);
listener.c.d;
listener.c.d = 1111;
listener.c.d;
console.log('lister<<<<<<<<', listener);
注释1:Object.defineProperty只能写对应的key,因此每次需要遍历整个对象所有的key,依次包裹。这也是Vue2的弊端——需要提前整体遍历对象,效率比较差。而且只能操作已有的数据。
注释2:如果对象包含了对象,需要做递归处理,而这一步,需要操作之前就要执行,效率差。
以上就是Vue2实现双向数据绑定的原理,总结来说,Vue2有以下弊端:
- 需要提前整体遍历对象每一个数据,效率低;
- 数组的方法无法触发set\get,因此Vue2对数组的方法做了重构;
- 只能对已有的数据做操作,如果新增数据,比如obj['aaa'],则无法获得监听,因为没有被Object.definedPropoty包裹。
那么Vue3做了什么呢?
// vue3
function isObject(obj: any) {
return typeof obj === 'object' && obj !== null;
}
function observer(dataSource: { [key: string]: any }) {
const proxy = new Proxy(dataSource, {
get(target, prop) {
if (isObject(target[prop as keyof typeof target])) {
return observer(target[prop as keyof typeof target]);
}
console.log('读取操作');
return target;
},
set(target, prop, val) {
if (target.prop === val) return;
if (isObject(target[prop as keyof typeof target])) {
return observer(target[prop as keyof typeof target]);
}
target[prop as keyof typeof target] = val;
console.log('更新操作');
return true;
},
});
return proxy;
}
Proxy可以自动监听整个对象,而且支持数组,无需提前遍历。
the end!