35.Proxy和Reflect详解

监听对对象的操作

  • 有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
  • 下面实现的弊端:
  • 如果我们想监听对对象更加丰富的操作,比如新增属性,删除属性,那么Object.defineProperty是无能为力的
使用Object.defineProperty()监听对对象进行操作
const obj = {
  name: "coderwhy",
  age: 18,
};

Object.keys(obj).forEach((key) => {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    set: function (newVal) {
      value = newVal;
      console.log(`监听到给${key}属性设置值`);
    },
    get: function () {
      console.log(`监听到获取${key}属性的值`);
      return value;
    },
  });
});

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

// 监听到获取name属性的值
// coderwhy
// 监听到给age属性设置值

Proxy基本使用

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

  • 如果我们希望监听对一个对象的相关操作,我们可以先创建一个这个对象的代理对象(Proxy对象)
  • 之后对改对象的所有操作,都通过代理对象完成,
  • 代理对象可以监听到我们想要对原对象进行哪些操作
new Proxy(target, handler)

target为要监听的目标对象
Proxy对象有13种默认的捕获器方法,
如果需要监听对原对象的某种操作,然后做出相应处理,可以在handler对象中重写对应的捕获器方法

使用Proxy监听对对象进行操作
const obj = {
  name: "coderwhy",
  age: 18,
};

//创建一个obj的代理对象
const objProxy = new Proxy(obj, {
  //重写proxy对象的get捕获器
  get(target, key) {
    console.log(`监听到获取obj对象的${key}属性的值`);
    return target[key];
  },
  //重写proxy对象的set捕获器
  set(target, key, newVal) {
    console.log(`监听到obj对象的${key}属性的值被重新赋值了`);
    target[key] = newVal;
  },
});

//对obj对象要做的所有操作,都对其代理对象操作,
console.log(objProxy.name);
objProxy.age = 30;

// 监听到获取obj对象的name属性的值
// coderwhy
// 监听到obj对象的age属性的值被重新赋值了

Proxy的13中捕获器用法

const obj = {
  name: "coderwhy",
  age: 18,
};

//创建一个obj的代理对象
const objProxy = new Proxy(obj, {
  //重写proxy对象的get捕获器:监听获取目标对象的属性值
  get(target, key, receiver) {
    //get捕获器有三个参数:
    //target为目标对象,key为当前操作的属性名,receiver为目标对象的代理对象即objProxy
    console.log(`监听到获取obj对象的${key}属性的值`);
    return target[key];
  },
  //重写proxy对象的set捕获器:监听设置目标对象的属性值
  set(target, key, newVal, receiver) {
    //set捕获器有四个参数:
    //target为目标对象,key为当前操作的属性名,newVal为给此属性设置的新值,receiver为目标对象的代理对象即objProxy
    console.log(`监听到obj对象的${key}属性的值被重新赋值了`);
    target[key] = newVal;
  },
  //监听对对象进行的in操作
  has: function (target, key) {
    console.log(`监听到对obj对象的${key}属性的in操作`);
    return key in target;
  },
  //监听对对象的delete操作的捕获器
  deleteProperty: function (target, key) {
    console.log(`监听到对obj对象的${key}属性的delete操作`);
    delete target[key];
  },
});

//对obj对象要做的所有操作,都对其代理对象操作,

//获取对象的属性值
console.log(objProxy.name);
//设置对象的属性值
objProxy.age = 30;

// in操作
console.log("name" in objProxy);

//delete操作
delete objProxy.age;
console.log(obj);

proxy对函数对象的监听

function foo(name, age) {
  this.name = name;
  this.age = age;
}

//创建foo的代理对象
const fooProxy = new Proxy(foo, {
  //监听对函数对象的apply调用
  apply(target, thisArg, argArray) {
    //target为目标对象,thisArg为给目标函数绑定的this,argArray为传给目标函数的参数数组
    console.log("对foo进行了apply调用");
    return target.apply(thisArg, argArray);
  },
  //监听对函数对象的new调用
  construct(target, argArray) {
    //target为目标对象,argArray为传给目标函数的参数数组
    console.log("对foo进行了new调用");
    return new target(...argArray);
  },
});

fooProxy.apply({}, ["why", "18"]);
new fooProxy("lily", 30);

另外七种捕获器用法:
下面为知识点扩展:

  • Object.getPrototypeOf(target)

    • target 目标对象
    • 获取目标对象的proto原型对象
  • Object.setPrototypeOf(target, prototype)

    • target 目标对象
    • 设置目标对象的proto原型对象为prototype
  • Object.getOwnPropertyDescriptor(target, prop)

    • 返回指定对象上一个自有属性对应的属性描述符。
    • target 目标对象
    • prop 目标属性
  • Reflect.ownKeys(target) 方法

    • 返回一个由目标对象自身的属性键组成的数组。
    • 它的返回值等同Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
  • Object.isExtensible(target) 方法

    • 判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
  • Object.preventExtensions(target)方法

    • 让一个对象变的不可扩展,也就是永远不能再添加新的属性。
const obj = { name: "why", age: 18 };
const objProxy = new Proxy(obj, {
  //监听对目标对象进行的Object.getPrototypeOf操作
  getPrototypeOf(target) {
    console.log("监听到了对obj进行的Object.getPrototypeOf操作");
    return Object.getPrototypeOf(target);
  },
  setPrototypeOf(target, prototype) {
    // target为目标对象 prototype为要被设置为目标对象原型对象的对象
    console.log("监听到了对obj进行的Object.setPrototypeOf操作");
    return Object.setPrototypeOf(target, prototype);
  },
  getOwnPropertyDescriptor(target, prop) {
    console.log("监听到了对obj进行的Object.getOwnPropertyDescriptor操作");
    return Object.getOwnPropertyDescriptor(target, prop);
  },
  defineProperty(target, property, descriptor) {
    console.log("监听到了对obj进行的Object.defineProperty操作");
    return Object.defineProperty(target, property, descriptor);
  },
  //监听对目标对象进行的Object.getOwnPropertySymbols或Object.getOwnPropertyNames或Reflect.ownKeys操作
  ownKeys(target) {
    console.log(
      "监听到了对obj进行的Object.getOwnPropertyNames/Object.getOwnPropertySymbols操作"
    );
    return Object.getOwnPropertyNames(target);
  },
  isExtensible(target) {
    console.log("监听到了对obj进行的Object.isExtensible操作");
    return Object.isExtensible(target);
  },
  preventExtensions(target) {
    console.log("监听到了对obj进行的Object.preventExtensions操作");
    return Object.preventExtensions(target);
  },
});
const objPrototype = Object.getPrototypeOf(objProxy);
console.log(objPrototype); // [Object: null prototype] {}

Object.setPrototypeOf(objProxy, { title: "讲师" });
Object.getOwnPropertyDescriptor(objProxy, "name");
Object.defineProperty(objProxy, "height", {
  value: "1.88",
  writable: true,
  enumerable: true,
  configurable: true,
});
Object.getOwnPropertyNames(objProxy);
Object.getOwnPropertySymbols(objProxy);
console.log(Reflect.ownKeys(objProxy));
console.log(Object.isExtensible(objProxy));
Object.preventExtensions(objProxy);
console.log(Object.isExtensible(objProxy));

知识拓展:
例:

const s = Symbol();
const obj = {
  name: "why",
  age: 30,
  [s]: "我是灵魂画手",
};

console.log(Reflect.ownKeys(obj)); //[ 'name', 'age', Symbol() ]
console.log(Object.getOwnPropertyNames(obj)); //[ 'name', 'age' ]
console.log(Object.getOwnPropertySymbols(obj)); //[ Symbol() ]
console.log(
  Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
); //[ 'name', 'age', Symbol() ]

Reflect

Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。
那么这个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对象上;

那么Object和Reflect对象之间的API关系,可以参考MDN文档:
https://developer.mozilla.org/zh�CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods
下表详细介绍了Object 和 Reflect API上可用方法之间的差异。请注意,如果API中不存在某种方法,则将其标记为N/A。

image.png

image.png

image.png

let s = Symbol();
const obj = {
  _name: "why",
  get name() {
    return this._name;
  },
  set name(newVal) {
    this._name = newVal;
  },
  s: "不可描述的秘密",
};

//为对象obj添加一个不可枚举属性work
Object.defineProperty(obj, "work", {
  value: "teacher",
});

// console.log(Object.getOwnPropertyDescriptors(obj))
console.log(Object.keys(obj)); // [ '_name', 'name', 's' ]
//Object.keys(target)返回目标对象所有可枚举属性名组成的数组
console.log(Reflect.ownKeys(obj)); //[ '_name', 'name', 's', 'work' ]
//Reflect.ownKeys(target)返回目标对象素有的属性名组成的数组

Refect和Proxy结合使用

const obj = {
  name: "why",
  age: 18,
};

const objProxy = new Proxy(obj, {
  get: function (target, key) {
    console.log(`监听到获取对象的${key}的属性值`)
    return Reflect.get(target, key);
  },
  set: function (target, key, newVal) {
    //Reflect.set的执行返回一个布尔值,如果设置值成功,为true,如果失败为false
    const result = Reflect.set(target, key, newVal);
    if (result) {
      console.log("设置值成功");
    } else {
      console.log("设置值失败");
    }
  },
});

objProxy.name = 'lily'
console.log(objProxy.name)

Reflect的receiver参数

const obj = {
  _name: "why",
  get name() {
    return this._name;
  },
  set name(newVal) {
    this._name = newVal;
  },
};

const objProxy = new Proxy(obj, {
  get(target, key) {
    console.log(`监听到了获取对象的${key}的值的操作`)
    return Reflect.get(target, key)
  }
})

console.log(objProxy.name)

image.png

分析:

  • objProxy.name 的执行会触发Proxy对象的get捕获器,执行Relect.get(target,key)
  • Relect.get(target,key)的执行 会执行对象的name属性的存取属性描述符的get方法,从而执行this._name,
  • this._name中的this为obj,则是通过obj._name获取_name属性的值,
  • 而没有通过代理对象获取_name属性的值==》objProxy._name,所以不会再触发Proxy的get捕获器
  • 而实际上,我们希望捕获每一次获取对象属性的值的操作,
  • 那么需要把对象的name属性的属性描述符的get方法中的this改为Proxy对象objProxy

Reflect.get(target, key, receiver)方法接受三个参数,

  • target: 操作的目标对象
  • key: 操作的属性
  • receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值。

所以可以 通过给Reflect.get传递receiver来改变目标对象中getter调用时的this指向
而Proxy对象的get捕获器接收的第三个参数receiver为该Proxy对象

const obj = {
  _name: "why",
  get name() {
    return this._name;
  },
  set name(newVal) {
    this._name = newVal;
  },
};

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`监听到了获取对象的${key}的值的操作`)
    return Reflect.get(target, key, receiver)
  }
})

console.log(objProxy.name)
image.png

Reflect.construct(target, argArray[, newTarget])

  • Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...argArray).
  • target
    被运行的目标构造函数
  • argArray
    类数组,目标构造函数调用时的参数。
  • newTarget 可选
    作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target
  • 返回值
    以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。
function Student(name, age) {
  this.name = name;
  this.age = age;
}
function Teacher() {}
const stu = new Student("lily", 18);
console.log(stu); //Student { name: 'lily', age: 18 } 可以看到创建了一个Student类的对象
console.log(stu.__proto__ === Student.prototype); //true

//现在有一个需求,执行Student方法,但是创建出来的是Teacher类的对象
var stu1 = Object.create(Teacher.prototype); //创建一个空对象,其__proto__指向Teacher.prototype
Student.apply(stu1, ["lily", 18]);
console.log(stu1); // Teacher { name: 'lily', age: 18 }
console.log(stu1.__proto__ === Teacher.prototype); //true

//下面的方式与上面的方式等效
const stu2 = Reflect.construct(Student, ["lily", 18], Teacher);
console.log(stu2); // Teacher { name: 'lily', age: 18 }
console.log(stu2.__proto__ === Teacher.prototype); //true

非常感谢王红元老师的深入JavaScript高级语法让我学习到很多 JavaScript 的知识

你可能感兴趣的:(35.Proxy和Reflect详解)