ES6中的 Proxy 和 Reflect

Proxy

1.监听对象的操作

有一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程。

  • ES6前可以通过属性描述符中的存取属性描述符(Object.defineProperty)来做;
  • 还有代理 Proxy

1.1 属性描述符实现

使用属性描述符中的存取属性描述符:get函数和set函数。

const obj = {
  name: "fct",
  age: 18
}

// 监听对象某个元素的改变
// Object.defineProperty(obj, "name", {
//   get() {
//     console.log("监听到obj对象的name属性被访问了")
//   },
//   set() {
//     console.log("监听到obj对象的name属性被设置值")
//   }
// });

// 监听对象所有元素的改变
Object.keys(obj).forEach(key => {
  let value = obj[key]

  Object.defineProperty(obj, key, {
    get() {
      console.log(`监听到obj对象的${key}属性被访问了`)
      return value
    },
    set(newValue) {
      console.log(`监听到obj对象的${key}属性被设置值`)
      value = newValue;
    }
  });
});

obj.name = "wyt";
obj.age = 21;

console.log(obj.name);
console.log(obj.age);

obj.height = 1.0;

缺点:

  • 首先,Object.defineProperty设计的初衷,不是为了去监听一个对象中所有的属性的。
    • 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。
  • 其次,如果我们想监听更加丰富的操作,比如新增属性删除属性,那么Object.defineProperty是无能为力的。
  • 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象。

1.2 Proxy监听

在ES6中,新增了一个Proxy 类,是用于帮助我们创建一个代理的:

  • 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);
  • 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行的操作;

基本使用:const proxyObj(代理对象) = new Proxy('对象名', 捕获器对象)

Proxy监听对象属性变化:

const obj = {
  name: "fct",
  age: 18
}

const objProxy = new Proxy(obj, {
  // 获取值时的捕获器
  get(target, key, receiver) {
    // target为侦听的对象,key为当前操作的属性key
    console.log(`监听到对象的${key}属性被访问了`, target)
    return target[key];
  },

  // 设置值时的捕获器
  set(target, key, newValue, receiver) {
    // newValue 为新属性值
    console.log(`监听到对象的${key}属性被设置值`, target)
    target[key] = newValue;
  }
})

console.log(objProxy.name);
console.log(objProxy.age);

objProxy.name = "wyt";
objProxy.age = 21;

console.log(obj.name);
console.log(obj.age);

2. Proxy的捕获器

  1. get(获取值)捕获器

  2. set(设置值)捕获器

  3. in操作捕获器

    const objProxy = new Proxy(obj, {
      // 监听in的捕获器
      has(target, key) {
        console.log(`监听到对象的${key}属性in操作`, target)
        return key in target;
      },
    });
    
    // in操作符,name属性是否在objProxy对象中
    console.log("name" in objProxy);
    
  4. delete删除捕获器

    const objProxy = new Proxy(obj, {
      // 监听delete的捕获器
      deleteProperty: function(target, key) {
        console.log(`监听到对象的${key}属性被删除操作`, target)
        delete target[key];
      }
    });
    
    // delete操作
    delete objProxy.name;
    
  5. Object.getPrototypeOf获取原型方法的捕获器 -----》getPrototypeOf()

  6. Object.setPrototypeOf设置原型方法的捕获器 -----》setPrototypeOf()

  7. Object.isExtensible判断对象是否可扩展方法的捕获器 -----》isExtensible()

  8. Object.preventExtensions阻止对象可扩展方法的捕获器 -----》preventExtensions()

  9. Object.getOwnPropertyDescriptor获取对象属性描述符方法的捕获器 -----》getOwnPropertyDescriptor()

  10. Object.defineProperty设置对象属性描述符方法的捕获器-----》defineProperty()

  11. Object.getOwnPropertyNames获取对象属性名方法和Object.getOwnPropertySymbols获取对象Symbol属性的捕获器-----》ownKeys()

  12. (函数对象) 函数调用操作的捕获器 -----》apply()

    function foo() {}
    
    const fooProxy = new Proxy(foo, {
      apply: function (target, thisArg, argArray) {
        console.log("对foo函数进行了调用")
        return target.apply(thisArg, argArray)
      }
    })
    
    fooProxy();
    fooProxy.apply({}, ["abc", "cba"]);
    
  13. (函数对象) new 操作符的捕获器 -----》construct

    function foo() {}
    
    const fooProxy = new Proxy(foo, {
      construct: function (target, argArray, newTarget) {
        console.log("对foo函数进行了new调用")
        return new target(...argArray)
      }
    })
    
    new fooProxy("abc", "cba");
    

Reflect

Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射

1. Reflect 作用

作用:

  • 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
  • 比如Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf()
  • 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()

我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

  • 这是因为在早期的ECMA规范中没有考虑到对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
  • 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
  • 另外还包含一些类似于in、delete操作符,让JS看起来是会有一些奇怪的;
  • 所以在ES6中新增了Reflect,让这些操作都集中到了Reflect对象上;

2. Reflect的方法

Reflect的方法和Proxy的捕获器方法是一一对应的。

使用:

const obj = {
  name: "fct",
  age: 18
}

const objProxy = new Proxy(obj, {
  get: function (target, key, receiver) {
    console.log("get---------")
    return Reflect.get(target, key)
  },
  set: function (target, key, newValue, receiver) {
    console.log("set---------")
    // target[key] = newValue
    // 拥有Boolean返回值
    const result = Reflect.set(target, key, newValue)
    if (result) {
    } else {
    }
  }
})

objProxy.name = "wyt"
console.log(objProxy.name)

3. receiver参数的作用

const obj = {
  _name: "fct",
  get name() {
    return this._name
  },
  set name(newValue) {
    this._name = newValue
  }
}

const objProxy = new Proxy(obj, {
  get: function (target, key, receiver) {
    // target 是源对象。即 obj对象
    // receiver是创建出来的代理对象,即当前 objProxy
    console.log("get方法被访问--------", key);
    // 不传递 receiver 参数,obj对象中的 this 指向 obj对象,不能对 _name属性进行拦截
    // return Reflect.get(target, key);
    // 传递 receiver 参数,this 指向 objProxy对象,可对 _name属性再进行拦截
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, newValue, receiver) {
    console.log("set方法被访问--------", key)
    Reflect.set(target, key, newValue);
    Reflect.set(target, key, newValue, receiver);
  }
})

console.log(objProxy.name);
objProxy.name = "wyt";

posted @ 2021-12-22 12:57  青柠i  阅读(37)  评论(0编辑  收藏  举报