extends的出现,使用内建对象的继承得以实现。Proxy能够拦截JS引擎内部目标的底层对象操做,这些底层操做被拦截后会触发响应特定操做的陷阱函数(traps),对于别人封装好的对象或内建对象,均可以自定义操做。
而反射(Reflect)API是以Reflect对象的形式出现的。API中的默认特征与相同的底层操做是一致的,而经过代理能够重写这些操做,每一个代理Trap,对应一个命名和参数值都相同的Reflect方法。
Reflect 中全部方法及说明--MDN数组
const target = {}; const proxy = new Proxy(target, {}); proxy.title = 'proxy'; console.log(proxy.title); console.log(target.title); target.title = 'target'; console.log(target.title); console.log(proxy.title);
很是简单的Proxy,没有实现任何自定义的Trap 方法,都是默认实现。app
let handler = { get: function(target, name){ return name in target ? target[name] : 37; } }; let p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37
来自于MDN的一个小例子,语法极其简单。get trap function 有3个参数:函数
而get trap 能作什么用呢?或者说,咱们在何时使用get呢?一个最主要的功能,验证对象结构。优化
对象结构:对象中全部可用的属性和方法集合。JS引擎经过对象结构来优化代码,一般会建立类来表示对象。
let proxy = new Proxy({}, { get(target, key, receiver) { if (!(key in receiver )) { throw new TypeError(`属性 ${key} 不存在`); } return Reflect.get(target, key, receiver); } }); proxy.name = 'proxy'; console.log(proxy.name); // proxy console.log(proxy.age); // 抛出 TypeError 异常
get是取值操做,而set就是赋值操做,能够对属性值进行验证。this
let target = { name: 'target' } let proxy = new Proxy(target, { set(target, key, value, receiver) { if (!(key in receiver )) { if (isNaN(value)) { throw new TypeError('属性必须为数字'); } } return Reflect.set(target, key, value, receiver); } }); proxy.count = 10; console.log(proxy.count); // 10 console.log(proxy.count); // 10 proxy.name = 'tom'; console.log(proxy.name); // tom console.log(target.name); // tom proxy.secondName = "Lee"; // 抛出 TypeError 异常
has trap function 接受两个参数prototype
能够用来隐藏已有属性。代理
// has trap let target = { name: 'target', age: 24 } let proxy = new Proxy(target, { has(target, key) { if (key === 'age'){ return false; } else { return Reflect.has(target, key); } } }); console.log('age' in proxy); // false console.log('age' in target); // true console.log('name' in proxy); // true console.log('name' in target); // true
delete操做能够从对象中移除属性,若是成功则返回true, 失败返回 false 。在严格模式下,删除一个不可配置属性,则会抛出错误,而在非严格模式下,只是返回false.code
let target = { name: 'target', age: 24 } let proxy = new Proxy(target, { deleteProperty(target, key) { if (key === 'age'){ return false; } else { return Reflect.deleteProperty(target, key); } } }); console.log( 'age' in proxy); const r1 = delete proxy.age; console.log(r1); console.log( 'name' in proxy); const r2 = delete proxy.name; console.log(r2);
这个要了解一点:Object.get/setPrototypeOf 与 Reflect.get/setPrototypeOf 的区别。
从功能上来说,两个操做是一致的。但从实现上来说,Object.get/setPrototypeOf 是高级方法,建立伊始就给开发者使用,而Reflect.get/setPrototypeOf 是更底层的操做,使开发者能够访问以前只在内部操做的[[Get/SetPrototypeOf]]。
面对两套方法选择时,可能会让人无所适从,不知道选哪一个更好。有一点能够确认的是,Object提供的方法更高级,是对Reflect方法的包裹器,最终仍是会调用Reflect方法,但在此以前,会执行一些额外的步骤,并经过检查返回值,来肯定下一步怎么操做。对象
对象可扩展,Object.isExtensible和Object.preventExtesions 方法,在ES5下已经实现,而ES6中经过 Reflect.isExtensible和Reflect.preventExtensions来实现。继承
var p = new Proxy({}, { isExtensible: function(target) { console.log('called'); return true;//也能够return 1;等表示为true的值 } }); console.log(Object.isExtensible(p)); // "called" // true var p = new Proxy({}, { preventExtensions: function(target) { console.log('called'); Object.preventExtensions(target); return true; } }); console.log(Object.preventExtensions(p)); // "called" // false
在代理中能够分别使用defineProperty陷阱和getOwnPropertyDescriptor陷阱拦截Object.defineProperty方法和Object.getOwnPropertyDescriptor方法的调用。
defineProperty
Object.defineProperty与Reflect.defineProperty方法返回值不一样,前一个返回第一个参数,后一方法,则返回操做的结果是否成功。
let target = {}; let r1 = Object.defineProperty(target, 'name', { value: 'target' }); console.log(r1 === target); // true let r2 = Reflect.defineProperty(target, 'name', { value: 'target'}); console.log(r2); // true
getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor 若是原始值被传入第一个参数,内部将会对这个值进行强制转换,转成Object对象,而Reflect.getOwnPropertyDescriptor方法,则会报出一个错误。
这个trap能够拦截内部[[OwnPropertyKeys]]方法,经过返回数组的值能够覆写其行为。返回的数组被用于Object.keys(),Object.getOwnPropertyNames(),Object.getOwnPropertySymbols()和Object.assign()。
let proxy = new Proxy({}, { ownKeys(target) { return Reflect.ownKeys(target).filter(key => { // console.log(key); // return true; // 能够和下面的返回作个对比,看看输出有什么不一样 return typeof key !== 'string' || key[0] !== '_'; }) } }); let nameSymbol = Symbol('name'); proxy.name = 'Tom'; proxy._name = 'Jerry'; proxy[nameSymbol] = 'Tom & Jerry'; const names = Object.getOwnPropertyNames(proxy), keys = Object.keys(proxy), symbols = Object.getOwnPropertySymbols(proxy); console.log(names.length); console.log(names); console.log(keys.length); console.log(keys); console.log(symbols.length); console.log(symbols);
MDN apply
MDN construct
这两个代理的目标,都是函数。
const target = function() { return 42; } const proxy = new Proxy(target, { apply: function(tt, ta, args) { return Reflect.apply(tt, ta, args); }, constructor(tt, ta, args) { return Reflect.constructor(tt, ta, args); } }); console.log(typeof proxy); console.log(proxy()); const instance = new proxy(); console.log(instance instanceof proxy); console.log(instance instanceof target);
因此,这两个trap,一般能够用来
例子就不一一写了,知道用法,很方便就能实现。
方法 Proxy.revocable() 建立一个可撤销的 Proxy 对象.
MDN revocable
var revocable = Proxy.revocable({}, { get: function(target, name) { return "[[" + name + "]]"; } }); var proxy = revocable.proxy; console.log(proxy.foo); // "[[foo]]" revocable.revoke(); console.log(proxy.foo); // TypeError is thrown proxy.foo = 1 // TypeError again delete proxy.foo; // still TypeError typeof proxy // "object", typeof doesn't trigger any trap
若是把代理当原型,将发生什么事呢?在JS中,一旦涉及到原型,事情每每就没那么简单了。若是原型是代理,而代理是透明的,仅当默认操做执行到原型上时,都会调用代理trap,这大大限制代理的能力。
const target = {}; const newTarget = Object.create(new Proxy(target, { defineProperty(trapTarget, name, desc) { return false; } })); // 按原型执行顺序,这个方法能够被调用到吗? Object.defineProperty(newTarget, 'name', { value: 'newTarget' }); console.log(newTarget.name); console.log(newTarget.hasOwnProperty('name')); // 能够看出,defineProperty方法,没有获得调用
在Object.create中,能够用如下几个trap
先看一个例子
function NoSuchProperty() {} NoSuchProperty.prototype = new Proxy({}, { get(tt, key, receiver) { throw new ReferenceError(`${key} doesn't exist `); } }); class Square extends NoSuchProperty { constructor(length, width) { super(); this.length = length; this.width = width; } } let shape = new Square(2, 6); const a1 = shape.length * shape.width; console.log(a1); // 12 let a2 = shape.length * shape.wdth; // 抛出异常
说明:wdth是个拼写错误,但按JS原型机制,shape中没有时,会去原型中查找,因此就会抛出异常。虽然代理不是shape的直接原型,存在于shape对象的原型链中。