Vue2 Object.defineProperty 与 Vue3 Proxy 的区别及实现原理
区别一:
Object.defineProperty 是劫持对象的属性,如果新增元素,那么Vue2需要再次调用 Object.defineProperty,而 Proxy 劫持的是整个对象,不需要做特殊处理。
Vue2里面使用的是 Object.defineProperty进行数据劫持,来对属性进行操作的时候可以获取到对应属性的 get 和 set,但是vue2这种数据劫持有个非常大的问题,比如给里面新增一些对象的时候,必须自己对这个对象再做一次 Object.defineProperty 处理,比如新增一个属性 {name, age} 属性, 后来动态给这个对象新增加一个height属性,那么这个height这个属性是不会做数据劫持的,要想做数据劫持就得再调一次 Object.defineProperty才可以,所以说对新增对象往往是无能为力的。 所以在Vue2提供了一些api 如Vue.$set(), 通过他可以再做一次Object.defineProperty劫持。
在Vue3的时候使用 Proxy 代理, Proxy它可以劫持整个对象,即使新增属性也可以帮助劫持。
区别二:
修改对象不同,Vue2使用 Object.defineProperty时,我们修改(get、 set、return) 返回的还是原来的Obj对象, Vue3 使用 Proxy的时候 返回的是(retunr new Proxy) 代理Proxy对象,修改的也是代理Proxy对象
区别三:
Proxy 能观察的类型比 Object.defineProperty 更丰富 除了 get set 还有如下:
- has: in 操作符的捕获器;
- deleteProperty: delete 操作符的捕获器 (delete info[name]) 等等其他操作
区别四
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化:性能要比 defineProperty更高,但是Proxy是不兼容ie浏览器
Vue2响应式实现 Object.defineProperty
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => {
effect();
})
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
// 1.根据对象(target)取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue2对raw进行数据劫持
function reactive(raw) {
Object.keys(raw).forEach(key => {
const dep = getDep(raw, key);
let value = raw[key];
Object.defineProperty(raw, key, {
get() {
dep.depend();
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
dep.notify();
}
}
})
})
return raw;
}
// 测试代码
const info = reactive({counter: 100, name: "wwww"});
const foo = reactive({height: 1.88});
// watchEffect1
watchEffect(function () {
console.log("effect1:", info.counter * 2, info.name);
})
// watchEffect2
watchEffect(function () {
console.log("effect2:", info.counter * info.counter);
})
// watchEffect3
watchEffect(function () {
console.log("effect3:", info.counter + 10, info.name);
})
watchEffect(function () {
console.log("effect4:", foo.height);
})
// info.counter++;
// info.name = "why";
foo.height = 2;
Vue3响应式实现 Proxy
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => {
effect();
})
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
// 1.根据对象(target)取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue3对raw进行数据劫持
function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key);
dep.depend();
return target[key];
},
set(target, key, newValue) {
const dep = getDep(target, key);
target[key] = newValue;
dep.notify();
}
})
}
// const proxy = reactive({name: "123"})
// proxy.name = "321";
// // 测试代码
// const info = reactive({counter: 100, name: "wwww"});
// const foo = reactive({height: 1.88});
// // watchEffect1
// watchEffect(function () {
// console.log("effect1:", info.counter * 2, info.name);
// })
// // watchEffect2
// watchEffect(function () {
// console.log("effect2:", info.counter * info.counter);
// })
// // watchEffect3
// watchEffect(function () {
// console.log("effect3:", info.counter + 10, info.name);
// })
// watchEffect(function () {
// console.log("effect4:", foo.height);
// })
// info.counter++;
// info.name = "hehe";
// foo.height = 2;