ES6精华:Proxy & Reflect

导语

本文主要介绍了ES6中ProxyReflect的精华知识,并附有恰当实例。Proxy意为代理器,经过操做为对象生成的代理器,实现对对象各种操做的拦截式编程。Reflect是一个包揽更为严格、健全的操做对象方法的模块。由于Proxy所能代理的方法和Reflect所包括的方法基本对应,并且在拦截方法里应该使用对应的Reflect方法返回结果,因此将二者合并在一块儿分享。编程

1 Proxy

1.1 登堂

先想个问题,如何管控对象某一属性的读取和修改(不涉及闭包建立私有属性)?
先建立不该被直接改动的容器属性:_属性名,再设置相应的settergetter函数或建立相应的操做方法。数组

--- 设置 setter 和 getter 函数
let obj = {
  _id: undefined,
  get id() {
    return this._id;
  },
  set id(v) {
    this._id = v;
  }
};
obj.id = 3; // 至关:obj._id = 3
console.log(obj.id); // console.log(obj._id);

--- 建立获取及修改方法
let obj = {
  _id: undefined,
  id() {
    if (!arguments.length) return this._id;
    this._id = arguments[0];
  }
};
obj.id(3); // 至关:obj._id = 3
console.log(obj.id()); // console.log(obj._id);

这样有明显的缺陷:要为每一个须要管控的属性进行重复设置,并且实际上容器属性能够被任意修改。
若是要求升级,咱们须要监听查看、删除、遍历对象属性的操做,怎么办?ES6以前只能凉拌,ES6以后Proxy代你办。闭包

let obj = { id: 0, name: 'Wmaker' };
let objProxy = new Proxy(obj, {
  get(target, attr) {
    console.log(`Get ${attr}`);
    return target[attr];
  },
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    target[attr] = val;
    return true;
  }
});
objProxy.id; // 打印出:Get id,至关:obj.id;
objProxy.name; // 打印出:Get name,至关:obj.name;

1.2 入室

如前节示例可知,Proxy是生成代理器的构造函数。传入的第一个参数为须要被代理的对象,第二个参数是须要拦截操做的配置对象(以后会列出全部可拦截的操做及其意义和注意事项)。配置对象中的每一个拦截操做,都有默认格式的传入参数,有些也要求有特定的返回值(下面会罗列出某些规律)。 app

生成的代理器是一个与被代理对象关联的代理实例,能够像操做普通对象同样对待它。便是说,能够被delete掉某个属性,能够被遍历或获取原型。全部做用于代理器的操做,都至关直接做用于被代理对象,还可为其配置拦截操做。说的激愤点,苍老师能作好的,咱们的小泽老师怎么会差呢?另外可代理的不仅仅是对象,属于对象的函数、数组都是无条件接受的。 函数

为对象生成代理器以后,依然能够操做原对象,但这天然是不建议的。this

参数
不一样拦截函数的传入参数不尽相同,但通常为被代理对象,该操做须要的参数等和代理器对象。
没必要记忆每一个拦截函数的参数,为脑瓜减减负担,使用时先打印出arguments查看便会一目了然。prototype

返回值
在拦截方法里,应尽可能使用Reflect对应的方法进行操做,并返回该方法的返回值。一方面是简单,更重要的是在不一样方法或某些环境下,对返回值有硬性的要求,不然直接报错。好比construct()必须返回一个对象,不然报错。再好比set()在严格模式下,必须返回true或可转化成true的值,不管操做是否成功。代理

"use strict";

--- 错误的作法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    return target[attr] = val;
  }
});

objProxy.id = 1; // 操做成功。
objProxy.id = 0; // 操做已经成功,但报错,再也不往下执行。

--- 推荐的作法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    return Reflect.set(target, attr, val);
  }
});

objProxy.id = 1; // 操做成功。
objProxy.id = 0; // 操做成功。

拦截方法的返回值并不会直接反映到外部,JS会进行某些验证和排除。
好比即使在ownKeys()中返回所有的值,但实际到外部的只有相应的系列。code

两次打印的结果不相等。

let obj = { id: 0, [Symbol.iterator]() {} };
let objProxy = new Proxy(obj, {
  ownKeys(target) {
    let res = Reflect.ownKeys(target);
    console.log('1', res);
    return res;
  }
});

console.log('2', Object.keys(objProxy));

限制性的延续
当被代理对象自身已有某些限制,好比不可扩展或属性不可写不可配置等。对象自己的操做已经受到了限制,这时若是执行相应的代理操做,天然会报错。好比当属性不可写时,若是代理并执行了set()操做,则会直接报错。对象

let obj = { id: 0 };
Object.defineProperty(obj, 'name', {
  value: 'Wmaker'
});

let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    return Reflect.set(target, attr, val);
  }
});

objProxy.id = 1; // 操做成功。
objProxy.name = 'Limo'; // 报错。

this
有些原生对象的内部属性或方法,只有经过正确的this才能访问,因此没法进行代理。
好比日期对象,new Proxy(new Date(), {}).getDate(),报错提示:这不是个Date对象。

也有变通的办法,好比对于须要正确 this 的方法能够这样作。

let p = new Proxy(new Date(), {
  get(target, attr) {
    const v = Reflect.get(target, attr);
    return typeof v === 'function'
      ? v.bind(target)
      : v;
  }
});
p.getTime(); // 没有错误。

处于配置对象中的this直接指向配置对象,不是被代理对象或代理器对象。
处于被代理对象中的this,分为存在于方法和存在于getter/setter中两种。二者获取this的方式不一样,咱们以实例说明。

--- 例一,没有 set 拦截操做。
let obj = {
  get id() {
    console.log('o', this);
  },
  fn() {
    console.log('o', this);
  }
};

let objProxy = new Proxy(obj, {});

objProxy.id; // 打印出 objProxy 。
objProxy.fn(); // 打印出 objProxy 。


--- 例二,有 set 拦截操做。实际使用了 target[attr] 获取属性值。
let obj = {
  get id() {
    console.log('o', this);
  },
  fn() {
    console.log('o', this);
  }
};

let objProxy = new Proxy(obj, {
  get(target, attr) {
    console.log('p', this);
    return target[attr];
  }
});

objProxy.id;
// 打印出配置对象和 obj。
// 由于实际是经过被代理对象即 target 访问到 id 值的。

objProxy.fn();
// 打印出配置对象和 objProxy。
// 能够等价的将方法转化成 (objProxy.fn).call(objProxy)。
// 虽然方法也是经过 target 访问到的,但对于方法来讲,环境变量一开始就肯定了。

原型为代理器
若是对象的原型为代理器,当操做进行到原型时,实际是操做原型的代理器对象。这时,其行为和普通代理器一致。

let obj = Object.create(new Proxy({}, {
  get(target, attr) {
    console.log('In proxy.');
    return Reflect.get(target, attr);
  }
}));

obj.name; // 打印出 In proxy. 。
// 当在实例上找不到对应属性时,转到了原型上,这时便被拦截了。

1.3 代理类别

这里仅仅是罗列出某些重点,详细的请看手册(标准和行为处于变更中)。

get
拦截属性的读取操做,包括数组取值。

set
拦截属性的赋值操做,包括数组赋值。
严格模式下,必须返回可转化为true的值。
严格模式下,若是代理对象有某些限制(属性不可写等),执行相应的拦截操做天然会报错。

apply
拦截函数的调用、callapply操做。

has
拦截判断对象是否具备某属性。
只对inReflect.has()方法有效,for in属于遍历系列。

construct
拦截new命令,必须返回一个对象。

deleteProperty
拦截delete属性操做。
严格模式下,必须返回可转化为true的值。
严格模式下,若是代理对象有某些限制(属性不可写等),执行相应的拦截操做天然会报错。

defineProperty
拦截Object.defineProperty(),不会拦截defineProperties
严格模式下,若是代理对象有某些限制(属性不可配置等),执行相应的拦截操做天然会报错。

getOwnPropertyDescriptor
拦截Object.getOwnPropertyDescriptor(),不会拦截getOwnPropertyDescriptors
必须返回一个对象或undefined,即返回与原方法相同的返回值,不然报错。

getPrototypeOf
拦截获取对象原型,必须返回一个对象或者null
若是对象不可扩展,必须返回真实的原型对象。
直接访问__proto__,经过各种方法等等都会触发拦截。

setPrototypeOf
拦截Object.setPrototypeOf()方法。

isExtensible
拦截Object.isExtensible()操做。
返回值必须与目标对象的isExtensible属性保持一致,不然会抛出错误。

preventExtensions
拦截Object.preventExtensions(),返回值会自动转成布尔值。

ownKeys
拦截对象自身属性的遍历操做。
好比keys(),getOwnPropertyNames(),getOwnPropertySymbols()等等。
返回值必须是数组,项只能是字符串或Symbol,不然报错。
返回值会根据调用方法的需求进行过滤,好比Object.keys()里不会有symbole
若是目标对象自身包含不可配置的属性,则该属性必须被返回,不然报错。
若是目标对象不可扩展,返回值必须包含原对象的全部属性,且不能包含多余的属性,不然报错。

1.4 Proxy.revocable

此静态方法也用于生成代理器对象的,但它还会返回一个回收代理器的方法。
使用场景:不容许直接访问目标对象,必须经过代理访问。并且一旦访问结束,就收回代理权,不容许再次访问。

let {proxy, revoke} = Proxy.revocable(obj, {});
revoke();
此时其内部属性值 [[IsRevoked]] 为 true,不能再操做代理器,不然报错。

2 Reflect

2.1 做用

最终目的是成为语言内部方法的宿主对象。
好比说defineProperty, getPrototypeOf, preventExtensions都很明显属于内部方法,不该挂在Object下。

提供替代命令式操做的相应函数。
使用Reflect.has(obj, attr)替代in操做
使用Reflect.deleteProperty(obj, attr)替代delete操做

使函数的行为更加完善和严格。
在没法定义属性时,Reflect.defineProperty返回false而不是抛出错误。
在要求类型是对象的参数为非对象时,会直接报错而不是调用Object()进行转化。

Porxy可拦截的方法对应,方便在实现自定义行为时,直接调用以完成默认行为。

2.2 静态方法

这里仅仅是罗列出某些重点,详细的请看手册

Reflect.get
Reflect.get(obj, attr, receiver)
返回属性值,没有则返回undefined
receiver是设置gettersetter里的this指向,默认指向obj

Reflect.set
Reflect.set(obj, attr, value, receiver)
设置属性值,返回布尔值。
注意:当该属性不是getter时,传入receiver等同于设置receiver对象上的属性值。

Reflect.has
Reflect.has(obj, attr)
等同attr in obj

Reflect.deleteProperty
Reflect.deleteProperty(obj, attr)
等同delete obj[attr]

Reflect.construct
Reflect.construct(func, args)
args等同于使用apply方法传入的参数数组。
提供了不使用new来调用构造函数的方法,等同new func(...args)

Reflect.getPrototypeOf
做用及参数等同Object.getPrototypeOf(obj)

Reflect.setPrototypeOf
做用及参数等同Object.setPrototypeOf(obj, newProto)

Reflect.apply
做用及参数等同Function.prototype.apply.call(func, thisArg, args)

Reflect.defineProperty
做用及参数等同Object.defineProperty(obj, attr, descriptor)

Reflect.getOwnPropertyDescriptor
做用及参数等同Object.getOwnPropertyDescriptor(obj, attr)

Reflect.isExtensible
Reflect.isExtensible(obj)
返回一个布尔值,表示当前对象是否可扩展。

Reflect.preventExtensions
Reflect.preventExtensions(obj)
设置对象为不可扩展,返回布尔值。

Reflect.ownKeys
Reflect.ownKeys(obj)
返回对象自己的全部属性。
等同于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

相关文章
相关标签/搜索