结论:
Proxy使用上比
Object.defineProperty
方便的多。从参数来看,
Proxy
代理整个对象,Object.defineProperty
只代理对象上的某个属性。如果对象内部要全部递归代理,则
Proxy
可以只在调用时递归,而Object.defineProperty
需要在一开始就全部递归,Proxy
性能优于Object.defineProperty
。对象上定义新属性时,
Proxy
可以监听到,Object.defineProperty
监听不到。数组新增删除修改时,
Proxy
可以监听到,Object.defineProperty
监听不到。
6.Proxy
不兼容IE,Object.defineProperty
不兼容IE8及以下
关于第4点举例
声明一个对象,使用以下两种代理方法:initDefineProp和createProxy;
const tmp = {a:1, b:[2,4,6],d:{e:10} };
function initDefineProp (){
Object.defineProperty(tmp,'c',{
set(value){
console.log('DefineProperty set:', value)
return value
}
})
}
function createProxy (data){
return new Proxy(data, {
get(target, prop, receiver){
console.log("Proxy get:", target[prop])
return target[prop]
},
set(target, prop, val,receiver){
target[prop] = val
console.log("Proxy set:", val)
return true
}
})
}
//分别用两种方式
// defineProperty
initDefineProp()
tmp.c = 3
console.log(tmp)
// 此时打印出
// {a:1, b:[2,4,6],d:{e:10} }
// 由于属性c是新增的的,并没有走到set方法,所以没打印‘'DefineProperty set:’
// Proxy
const tmpProxy = createProxy(tmp)
tmpProxy.c = 3
console.log(tmp)
console.log(tmpProxy)
// 此时打印出
// Proxy set: 3
// { a: 1, b: Array(3), d: {e:10}, c: 3}
// Proxy {a: 1, b: Array(3), d: {e:10}, c: 3}
但是如果修改tmp.d.e
就收集不到变化,原因是以上两种方式只能劫持对象的第一层属性,若想达到对所有属性的监控需要对其进行遍历
const tmp = {a:1, b:[2,4,6],d:{e:10} };
// proxy
function getProxy(data){
let handler={
get:function(obj,prop){
const v = Reflect.get(obj,prop);
console.log("Proxy get:", obj[prop])
if(v !== null && typeof v === 'object'){
return new Proxy(v,handler);//代理内层
}else{
return v; // 返回obj[prop]
}
},
set(obj,prop,value){
console.log("Proxy set:", value)
return Reflect.set(obj,prop,value);//设置成功返回true
}
};
return new Proxy(data,handler);
}
let tmpProxy = getProxy(tmp);
tmpProxy.d.e = 5;
console.log(tmpProxy) // {a:1, b:[2,4,6],d:{e:5} }
// defieProperty
const tmp = {a:1, b:[2,4,6],d:{e:10} };
function defineProperty(obj, key, val){
observer(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 读取方法
console.log('DefineProperty get:', val)
return val
},
set(newval) {
// 赋值监听方法
if (newval === val) return
observer(newval)
console.log('DefineProperty set:', newval)
val = newval
// 可以执行渲染操作
}
})
}
function observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
for (const key in obj) {
// 给对象中的每一个方法都设置响应式
this.defineProperty(obj, key, obj[key])
}
}
observer(tmp);
tmp.d.e = 5;
console.log(tmp)
// DefineProperty get: {e: 5}
// DefineProperty set: 5
// {a:1, b:[2,4,6],d:{e:5} }
关于第5点举例
以上面定义的两段函数举例
// 当执行以下代码
let tmpProxy = getProxy(tmp);
tmpProxy.b.push(555);
console.log(tmpProxy)
// 会打印出 Proxy set: 555 ,说明proxy拦截到了push的方法,执行了set里面的内容
// 当执行以下代码
this.observer(tmp)
tmp.b.push(555);
console.log(tmp)
// 不会打印出 DefineProperty set: ,说明defineProperty并没有拦截到了push的方法,及没有执行set里面的内容
如果想要可以监听需要重写数组方法、
const orginalProto = Array.prototype;
const arrayProto = Object.create(orginalProto); // 先克隆一份Array的原型出来
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method => {
arrayProto[method] = function () {
// 执行原始操作
orginalProto[method].apply(this, arguments)
console.log('数组原生方法监听赋值成功', method)
}
})
原理就是重写数组的七个原始方法,当使用者执行这些方法时,我们就可以监听到数据的变化,然后做些跟新操作,下面我们在observer中加上关于对数组的判断
function observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
if (Array.isArray(obj)) {
// 如果是数组, 重写原型
obj.__proto__ = arrayProto
// 传入的数据可能是多维度的,也需要执行响应式
for (let i = 0; i < obj.length; i++) {
observer(obj[i])
}
} else {
for (const key in obj) {
// 给对象中的每一个方法都设置响应式
defineProperty(obj, key, obj[key])
}
}
}
// 当执行以下代码
this.observer(tmp)
tmp.b.push(555);
console.log(tmp)
//打印出:数组原生方法监听赋值成功 及可以通过这种方式进行监听
之前一直说Vue2.x因为使用Object.defineProperty所以导致双向绑定存在以下问题:
- 无法检测数组/对象的新增
- 无法检测通过索引改变数组的操作
vue内部也重写了“push、pop、shift、unshift、splice、sort、reverse”这几种方法,所以可以解决问题1;针对问题2,其实也不是说无法检测这种的数组操作 a[1] = 2,将上述代码中判断Array.isArray(obj)之后对其在进行key in obj的遍历也可以达到要求;
funxtion observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
for (const key in obj) {
this.defineProperty(obj, key, obj[key])
}
}
const test ={a:1, b:[2,4,6],d:{e:10} };
observer(tmp)
test.b[1] = 999;
// 打印出 DefineProperty set: 999 成功
test.b[[3] = 'last';
// 并不会打印DefineProperty set: ,因为数组只有三项,直接操作下标3实际上是新增了元素,监听不到。
但是vue并没有这么实现,查了一下是因为性能方面的问题