Object.defineProperty & Proxy

结论:

  1. Proxy使用上比Object.defineProperty方便的多。

  2. 从参数来看,Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性。

  3. 如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于Object.defineProperty

  4. 对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。

  5. 数组新增删除修改时,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所以导致双向绑定存在以下问题:
  1. 无法检测数组/对象的新增
  2. 无法检测通过索引改变数组的操作

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并没有这么实现,查了一下是因为性能方面的问题


image.png

你可能感兴趣的:(Object.defineProperty & Proxy)