本文主要介绍了ES6中Proxy
和Reflect
的精华知识,并附有恰当实例。Proxy
意为代理器,经过操做为对象生成的代理器,实现对对象各种操做的拦截式编程。Reflect
是一个包揽更为严格、健全的操做对象方法的模块。由于Proxy
所能代理的方法和Reflect
所包括的方法基本对应,并且在拦截方法里应该使用对应的Reflect
方法返回结果,因此将二者合并在一块儿分享。编程
先想个问题,如何管控对象某一属性的读取和修改(不涉及闭包建立私有属性)?
先建立不该被直接改动的容器属性:_属性名,再设置相应的setter
和getter
函数或建立相应的操做方法。数组
--- 设置 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;
如前节示例可知,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. 。 // 当在实例上找不到对应属性时,转到了原型上,这时便被拦截了。
这里仅仅是罗列出某些重点,详细的请看手册(标准和行为处于变更中)。
get
拦截属性的读取操做,包括数组取值。
set
拦截属性的赋值操做,包括数组赋值。
严格模式下,必须返回可转化为true
的值。
严格模式下,若是代理对象有某些限制(属性不可写等),执行相应的拦截操做天然会报错。
apply
拦截函数的调用、call
和apply
操做。
has
拦截判断对象是否具备某属性。
只对in
和Reflect.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
。
若是目标对象自身包含不可配置的属性,则该属性必须被返回,不然报错。
若是目标对象不可扩展,返回值必须包含原对象的全部属性,且不能包含多余的属性,不然报错。
此静态方法也用于生成代理器对象的,但它还会返回一个回收代理器的方法。
使用场景:不容许直接访问目标对象,必须经过代理访问。并且一旦访问结束,就收回代理权,不容许再次访问。
let {proxy, revoke} = Proxy.revocable(obj, {}); revoke(); 此时其内部属性值 [[IsRevoked]] 为 true,不能再操做代理器,不然报错。
最终目的是成为语言内部方法的宿主对象。
好比说defineProperty, getPrototypeOf, preventExtensions
都很明显属于内部方法,不该挂在Object
下。
提供替代命令式操做的相应函数。
使用Reflect.has(obj, attr)
替代in操做
。
使用Reflect.deleteProperty(obj, attr)
替代delete操做
。
使函数的行为更加完善和严格。
在没法定义属性时,Reflect.defineProperty
返回false
而不是抛出错误。
在要求类型是对象的参数为非对象时,会直接报错而不是调用Object()
进行转化。
与Porxy
可拦截的方法对应,方便在实现自定义行为时,直接调用以完成默认行为。
这里仅仅是罗列出某些重点,详细的请看手册。
Reflect.get
Reflect.get(obj, attr, receiver)
返回属性值,没有则返回undefined
。 receiver
是设置getter
和setter
里的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.getOwnPropertyNames
与Object.getOwnPropertySymbols
之和。