Proxy 对象
Proxy 用来修改某些默认操作,等同于在语言层面做出修改。所以属于一种元编程(meta programming), 即对编程语言进行编程。字面理解为Proxy代理了某些默认的操作。
其使用格式如下:
var proxy = new Proxy(target, handler);
target是被代理的目标对象,handler也是个对象,用来定制拦截行为,内部定义每个被代理的行为。
注意:
- 如果希望这个代理有效,需要在 proxy 对象上调用属性方法,而不是在 target 上调用
- 如果指定 handler 为空对象,那么得到对象和原对象一样
- 得到的 proxy 是 target 的引用,如果没有代理,在 proxy 上的修改和在 target 上的修改等同
看一个简单的实例
var proxy = new Proxy({},{get: function(target, key){return 35;}});console.log(proxy.time); //35console.log(proxy.name); //35console.log(proxy.title); //35//被代理的对象无论输入什么属性都返回35
实际上,proxy 对象也可以被继承:
var proxy = new Proxy({},{get: function(target, key){return 35;}});var obj = Object.create(proxy);obj.time = 20;console.log(obj.time); //20console.log(obj.name); //35
感受一下它的威力:
var obj = new Proxy({}, {get: function(target, key, receiver){console.log(`getting ${key} ...`);return Reflect.get(target, key, receiver);},set: function(target, key, value, receiver){console.log(`setting ${key} ...`);return Reflect.set(target, key, value, receiver);}});obj.count = 1; //setting count ...++obj.count; //getting count ...//setting count ...console.log(obj.count); //getting count ...//2
可以看出来,handler对象中 get 方法表示属性的访问请求,set 方法表示属性的写入请求。
当然不仅仅 get 和 set, 我们可以定义以下拦截函数:
- get(target, propKey, receiver = target)
拦截对象的读取属性。当 target 对象设置了 propKey 属性的 get 函数时,receiver 绑定 get 函数的 this。返回值任意 - set(target, propKey, value, receiver = target)
拦截对象的写入属性。返回一个布尔值 - has(target, propKey)
拦截 propKey in proxy 操作符,返回一个布尔值 - deleteProperty(target, propKey)
拦截 delete proxy[propKey] 操作符,返回一个布尔值 - enumerate(target)
拦截 for(let i in proxy) 遍历器,返回一个遍历器 - hasOwn(target, propKey)
拦截 proxy.hasOwnProperty(‘foo’),返回一个布尔值 - ownKeys(target)
拦截 Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy),返回一个数组。该方法返回对象所有自身属性,包括不可遍历属性,不包括 Symble属性,但是Object.keys(proxy)不应该包括不可遍历属性 - getOwnPropertyDescriptor(target, propKey)
拦截 Object.getOwnPropertyDescriptor(proxy, propKey),返回其属性描述符 - defineProperty(target, propKey, propDesc)
拦截 Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDesc),返回一个布尔值 - preventExtensions(target)
拦截 Object.preventExtensions(proxy),返回一个布尔值 - getPrototypeOf(target)
拦截 Object.getPrototypeOf(proxy),返回一个对象 - isExtensible(target)
拦截 Object.isExtensible(proxy),返回一个布尔值 - setPrototypeOf(target, proto)
拦截 Object.setPrototypeOf(proxy, proto),返回一个布尔值 - apply(target, object, args)
拦截对 proxy 实例的函数操作,包括 proxy(…args),proxy.call(object, …args),proxy.apply(object, args) - construct(target, args, proxy)
拦截用 new 调用 proxy 函数的操作,construct()返回的不是对象会报错
以下列举一些 Proxy 的实例
访问对象不存在的属性报错
var obj = new Proxy({}, {get: function(target, key){if(key in target){return Reflect.get(target, key);} else {throw new ReferenceError(`"${key}" is not in object`);}}});obj.look = "picture";console.log(obj.look); //"picture"console.log(obj.sleep); //ReferenceError: "sleep" is not in object
数组索引为负时返回倒数位置的值
var origin = [10,20];var arr = new Proxy(origin, {get(target, key){let index = parseInt(key);if(index < 0){index = target.length + index;if(index < 0) return undefined;}return Reflect.get(target, index);}});console.log(arr[0]); //10console.log(arr[1]); //20console.log(arr[2]); //undefinedconsole.log(arr[-1]); //20console.log(arr[-4]); //undefined
保护对象内以 “_” 开头的属性为私有属性:
var o = {"_name": "Bob","age": 13,"_fun": function(){console.log("_fun is called");}};var obj = new Proxy(o, {get(target, key){if(key.charAt(0) === '_'){return undefined;}return Reflect.get(target, key);},set(target, key, value){if(key.charAt(0) === '_'){throw new Error('Cannot define a property begin with "_"');}return Reflect.set(target, key, value);},has(target,key){if(key.charAt(0) === '_'){return false;}return Reflect.has(target, key);},deleteProperty(target,key){if(key.charAt(0) === '_'){return false;} else {Reflect.deleteProperty(..arguments);}},apply(target,ctx,args){if(target.name.charAt(0) === '_'){throw new TypeError(`${target.name} is not defined`);} else {Reflect apply(...arguments);}},defineProperty(target,key,desc){if(key.charAt(0) === '_'){return new Error(`cannot define property begin with "_"`);} else {Reflect.defineProperty(..arguments);}},setPrototypeOf(target,proto){throw new TypeError(`Cannot change the proto of ${target}`);},construct(target,ctx,args){if(target.name.charAt(0) === '_'){throw new TypeError(`${target.name} is not defined`);} else {Reflect construct(...arguments);}}});console.log(obj.age); //13obj.age = 20;console.log(obj.age); //20console.log(obj._name); //undefinedobj._hobby = "Coding"; //Error: Cannot define a property begin with "_"_name in key //falsedelete obj._name;Object.defineProperty(obj,"_hobby",{value: "Coding"});Object.defineProperties(obj,{'_hobby': {value: "Coding"}});obj._fun();var a = new obj._fun();obj.__proto__ = {}; //Cannot define a property begin with "_"Object.setPrototypeOf(obj,{}) //Cannot change the proto of obj
当然不是所有 proxy 代理都不可取消,下面方法设置的代理是可以通过定义代理时返回的revoke函数取消:
var a = {name:"Bob"};var {proxy, revoke} = Proxy.revocable(a, {get(target,key){return undefined;}});proxy.name; //undefined;revoke();proxy.name; //TypeError: Cannot perform 'get' on a proxy that has been revoked
Reflect 对象
Reflect 对象有一下作用:
- 将 Object对象的一些明显属于语言层面的方法部署在 Reflect 上
- 修改某些 Object 对象的方法使其更合理。比如
Object.defineProperty遇到无法定义属性时会抛出错误,而Reflect.defineProperty会返回 false - 把所以 object 的操作都替换成函数行为,比如用
Reflect.has(obj,name)替换name in obj - 保证只要是 Proxy 有的方法就一定可以在 Reflect 上找到相同的方法,这样可以在实现 proxy 时方便的完成默认行为。换言之,无论 proxy 怎么修改默认行为,你总可以在 Reflect 上找到真正默认的行为
代理在添加额外的功能时,利用 Reflect 保证了原始功能的实现。举个例子:
var loggedObj = new Proxy({}, {get(target,propKey){console.log(`getting ${target}.${propKey}`); //当然你最好把操作记录到一个 log 中return Reflect.get(target,propKey);}});
Reflect有以下方法:
- Reflect.getOwnPropertyDescriptor(target, propKey)
等同于ObjectgetOwnPropertyDescriptor(target, propKey) - Reflect.defineProperty(target,propKey,desc)
等同于Object.defineProperty(target,propKey,desc) - Reflect.getOwnPropertyNames(target)
等同于Object.getOwnPropertyNames(target) - Reflect.getPrototypeOf(target)
等同于Object.getPrototypeOf(target) - Reflect.setPrototypeOf(target, proto)
等同于Object.setPrototypeOf(target, proto) - Reflect.deleteProperty(target, propKey)
等同于delete target.propKey - Reflect.enumerate(target)
等同于for ... in target - Reflect.freeze(target)
等同于Object.freeze(target) - Reflect.seal(target)
等同于Object.seal(target) - Reflect.preventExtensions(target)
等同于Object.preventExtensions(target) - Reflect.isFrozen(target)
等同于Object.isFrozen(target) - Reflect.isSealed(target)
等同于Object.isSealed(target) - Reflect.isExtensible(target)
等同于Object.isExtensible(target) - Reflect.has(target, propKey)
等同于propkey in object - Reflect.hasOwn(target, propKey)
等同于target.hasOwnProperty(propKey) - Reflect.ownKeys(target)
遍历得到target自身所有属性,包括不可枚举属性,不包括 Symbol 属性 Reflect.get(target,propKey, receiver = target)
如果 propKey 是个读取器,则读取器中的 this 绑定到 receivervar per = {bar: function(){console.log("per-bar")}}var obj = {get foo(){ this.bar(); },bar: function (){console.log("obj-bar")}};Reflect.get(obj, "foo", per); //"per-bar"
Reflect.set(target,propKey, value, receiver = target)
如果 propKey 是个读取器,则读取器中的 this 绑定到 receiver- Reflect.apply(target, thisArg, args)
等同于Function.prototype.apply.call(target, thisArg, args)即thisArg.target(args) - Reflect.construct(target,args)
等同于new target(...args)
注意以上方法中,Reflect.set(), Reflect.defineProperty(), Reflect.freeze(), Reflect.seal(), Reflect.preventExtensions() 在成功时返回 true, 失败时返回 false。对应的 Object 方法失败时会抛出错误。
由于 ES6 中引入了许多数据结构, 算上原有的包括Object, Array, TypedArray, DataView, buffer, Map, WeakMap, Set, WeakSet等等, 数组需要一个东西来管理他们, 这就是遍历器(iterator)。
