js的数据劫持与代理

2019/11/21 javascript

主要记录一下Object.defineProperty()和Proxy对象

1、Object.defineProperty()

语法:Object.defineProperty(obj, prop, descriptor);

obj: 要在其上定义属性的对象。 prop: 要定义或修改的属性的名称。 descriptor: 将被定义或修改的属性描述符。

返回被传递给函数的对象

1.1 属性描述符

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。

MDN:

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

enumerable:当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。 数据描述符同时具有以下可选键值:

writable:当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。

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

get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。

ser:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。

使用:

  Object.defineProperty(obj, "key", {
    enumerable: false,
    configurable: false,
    writable: false,
    value: "static"
  });

2、实现一个简易的vue数据劫持

使用Object.defineProperty的缺陷是性能,因为需要递归,而且无法监控数组长度的变化,而且对象如果后面有新增无法监控到,这也是vue有一个vm.$set方法的原因。

  function observer(obj) {
    // 缺陷就是无法监控数组的变化
    if (typeof obj !== "object" || obj == null) {
      return;
    }
    for (let key in obj) {
      // 因为defineProperty 需要一个公共的值去修改
      defineReactive(obj, key, obj[key]);
    }
  }
  let updateView = () => {
    // 更新方法
    console.log("更新");
  };
  function defineReactive(obj, key, value) {
    // Object.defineProperty
    observer(value); // 递归增加getter和setter
    Object.defineProperty(obj, key, {
      get() {
        return value;
      },
      set(val) {
        updateView();
        value = val;
      }
    });
  }
  observer(obj);
  obj.a.a = 100;
  console.log(obj.a); 

解释:

  1. 调用一个函数传入一个对象,如果不是对象的话就返回
  2. 如果是对象就去循环它,给它的每个值进行劫持。
  3. 每个值的值仍然可能是对象,所以递归一次,如果不是就默认返回了,如果是则进行这个流程。
  4. 给每个值数据劫持,如果是获取则返回,如果是更新则触发更新的方法。

3、proxy

ES6的新方法——代理: 好处是支持修改数组,可以自由决定是否拦截某个属性,性能也更高,缺点兼容性差.

语法:let p = new Proxy(target, handler);

target:用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。

返回一个proxy实例,可以再次操作。

  let obj = {
      a:{a:111}
  }
  let handler = { // 只能代理当前这个对象 1层, 如果多层只能递归。
      get(target,key){
        // 如果有多层,下一层的set方法会先来上一层的get里取值,所以我们在get里面加一层监控,如果来取值且为对象的话,就执行返回一个proxy,返回一个当前值的代理
        if(typeof target[key] === 'object'){
            return new Proxy(target[key],handler); // 如果是对象 就返回这个对象的代理
        }
        return Reflect.get(target,key);
      },
      set(target,key,value){
          // target[key] = value;
          if(key === 'length') return true;
          return  Reflect.set(target,key,value);
      }
  }
  let proxy = new Proxy(obj, handler)
  proxy.a.a = 222
  console.log(obj.a.a);

3.1 Reflect 反射

proxy经常和Reflect组合使用。Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

常用方法:

  1. Reflect.get():获取对象身上某个属性的值,类似于 target[name]。
  2. Reflect.has():判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
  3. Reflect.set():将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
  4. Reflect.apply():对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。

Search

    Table of Contents