美文网首页vue
Object.defineProperty 和 proxy 区别

Object.defineProperty 和 proxy 区别

作者: 抽疯的稻草绳 | 来源:发表于2021-01-22 15:54 被阅读0次

    Object.defineProperty

    Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。IE8不兼容。

    Object.defineProperty(obj, prop, descriptor)

    参数
    obj: 要定义属性的对象。
    prop: 要定义或修改的属性的名称或 Symbol 。
    descriptor: 要定义或修改的属性描述符。

    返回值
    被传递给函数的对象。

    Object.defineProperty(obj, 'name', {
      // configurable: false,//默认为false
      // enumerable: false,//默认为false
      value: 'kongzhi',
      // writable: false,/默认为false
      // get(){},
      // set(v){}
    });
    
    

    定义了 valuewritable , 一定不能有 getset, 反之亦然, 否则报错

    对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符
    数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
    存取描述符是由 getter 函数和 setter 函数所描述的属性。
    一个描述符只能是这两者其中之一;不能同时是两者

    这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty() 定义属性时的默认值):

    默认值:

    configurable
    当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
    默认为 false。

    enumerable
    当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
    默认为 false。

    数据描述符还具有以下可选键值:

    value
    该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
    默认为 undefined。

    writable
    当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
    默认为 false。

    存取描述符还具有以下可选键值:

    get
    属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
    默认为 undefined。

    set
    属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
    默认为 undefined。

    描述符默认值汇总

    拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
    属性值和函数的键 value、get 和 set 字段的默认值为 undefined。

    描述符可拥有的键值

    - configurable enumerable value writable get set
    数据描述符 可以 可以 可以 可以 不可以 不可以
    存取描述符 可以 可以 不可以 不可以 可以 可以
    let obj={a:2,b:{c:3}};
    Object.defineProperty(obj,'a',{
      configurable:true,
      enumerable:true,
      get(){
        return obj['a']
      },
      set(value){
        return obj['a']=value;
      }
    })
    
    
    let obj={a:2,b:{c:3}};
    Object.defineProperty(obj,'b',{
      writable:false,
      configurable:false
    })
    obj.b=3;
    obj.b.c=4;
    console.log(obj)//{a:2,b:{c:4}}
    
    
    const obj = {};
    Object.defineProperty(obj, 'name', {
      // configurable: false,//默认为false
      // enumerable: false,//默认为false
      value: 'kongzhi',
      // writable: false,/默认为false
      // get(){},
      // set(v){}
    });
    console.log(obj.name); // 输出 kongzhi
    
    // 改写obj.name 的值
    obj.name = 111;
    console.log(obj.name); // 还是打印出 kongzhi,因为writable默认为false
    
    
    var o = {};
    
    o.a = 1;
    // 等同于:
    Object.defineProperty(o, "a", {
      value: 1,
      writable: true,
      configurable: true,
      enumerable: true
    });
    
    // 另一方面,
    Object.defineProperty(o, "a", { value : 1 });
    // 等同于:
    Object.defineProperty(o, "a", {
      value: 1,
      writable: false,
      configurable: false,
      enumerable: false
    });
    
    

    全局变量Date不可修改,为了安全性考虑

    Object.defineProperty(window,'Date',{
      writable:false,
      configurable:false
    })
    
    

    Object.defineProperties

    Object.defineProperties本质上定义了obj 对象上props的可枚举属性相对应的所有属性。

    var obj = {};
    Object.defineProperties(obj, {
      'property1': {
        value: true,
        writable: true
      },
      'property2': {
        value: 'Hello',
        writable: false
      }
      // etc. etc.
    });
    
    

    Proxy

    Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。IE不兼容

    const p = new Proxy(target, handler)

    参数
    target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
    handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

    返回值
    一个Proxy代理的对象,操作这个对象会触发handler对应操作。改变原始对象不会触发。

    handler 对象的方法

    handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

    所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

    handler.getPrototypeOf()
    handler.setPrototypeOf()
    handler.isExtensible()
    handler.preventExtensions()
    handler.getOwnPropertyDescriptor()
    handler.defineProperty()
    handler.has()//in 操作符的捕捉器。
    handler.get(target, property)
    handler.set(target, property, value)
    handler.deleteProperty()//delete 操作符的捕捉器。
    handler.ownKeys()
    handler.apply()
    handler.construct()//new 操作符的捕捉器。
    
    

    基本操作

    const handler = {
        get: function(obj, prop) {
            return prop in obj ? obj[prop] : 37;
        }
    };
    
    const p = new Proxy({}, handler);
    p.a = 1;
    p.b = undefined;
    
    console.log(p.a, p.b);      // 1, undefined
    console.log('c' in p, p.c); // false, 37
    
    

    Proxy只代理对象外层属性

    let obj={a:1,b:{c:2}};
    let handler={
      get:function(obj,prop){
        const v = Reflect.get(obj,prop);
        return v; // 返回obj[prop]
      },
      set(obj,prop,value){
        return Reflect.set(obj,prop,value);//设置成功返回true
      }
    };
    let p=new Proxy(obj,handler);
    
    p.a//会触发get方法
    p.b.c//会触发get方法获取p.b,不会触发.c的set,因为c没被代理。
    
    

    递归代理对象内部对象

    let obj={a:1,b:{c:2}};
    let handler={
      get:function(obj,prop){
        const v = Reflect.get(obj,prop);
        if(v !== null && typeof v === 'object'){
          return new Proxy(v,handler);//代理内层
        }else{
          return v; // 返回obj[prop]
        }
      },
      set(obj,prop,value){
        return Reflect.set(obj,prop,value);//设置成功返回true
      }
    };
    let p=new Proxy(obj,handler);
    
    p.a//会触发get方法
    p.b.c//会先触发get方法获取p.b,然后触发返回的新代理对象的.c的set。
    
    

    可以看出Proxy代理对象时只会在调用时递归,不会一开始就全部递归,优化了性能。

    Proxy对象和原始对象

    let target = {};
    let p = new Proxy(target, {});
    
    p.a = 37;   // 操作转发到目标
    
    console.log(target.a);    // 37\. 操作已经被正确地转发
    
    target.a=4;
    console.log(p.a)//4
    
    console.log(target==p)//false
    
    

    总结

    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及以下。

    三、总结
    Object.defineProperty只能遍历对象属性进行劫持

    function observe(obj) {
        if (typeof obj !== 'object' || obj == null) {
            return
        }
        Object.keys(obj).forEach(key => {
            defineReactive(obj, key, obj[key])
        })
    }
    

    Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

    function reactive(obj) {
        if (typeof obj !== 'object' && obj != null) {
            return obj
        }
        // Proxy相当于在对象外层加拦截
        const observed = new Proxy(obj, {
            get(target, key, receiver) {
                const res = Reflect.get(target, key, receiver)
                console.log(`获取${key}:${res}`)
                return res
            },
            set(target, key, value, receiver) {
                const res = Reflect.set(target, key, value, receiver)
                console.log(`设置${key}:${value}`)
                return res
            },
            deleteProperty(target, key) {
                const res = Reflect.deleteProperty(target, key)
                console.log(`删除${key}:${res}`)
                return res
            }
        })
        return observed
    }
    

    Proxy可以直接监听数组的变化(push、shift、splice)

    const obj = [1,2,3]
    const proxtObj = reactive(obj)
    obj.psuh(4) // ok
    

    Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的

    正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)

    // 数组重写
    const originalProto = Array.prototype
    const arrayProto = Object.create(originalProto)
    ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
      arrayProto[method] = function () {
        originalProto[method].apply(this.arguments)
        dep.notice()
      }
    });
    
    // set、delete
    Vue.set(obj,'bar','newbar')
    Vue.delete(obj),'bar')
    

    Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9

    相关文章

      网友评论

        本文标题:Object.defineProperty 和 proxy 区别

        本文链接:https://www.haomeiwen.com/subject/xabczktx.html