Proxy 用來修改某些默認操做,等同於在語言層面作出修改。因此屬於一種元編程(meta programming), 即對編程語言進行編程。字面理解爲Proxy代理了某些默認的操做。
其使用格式以下:編程
var proxy = new Proxy(target, handler);
target是被代理的目標對象,handler也是個對象,用來定製攔截行爲,內部定義每一個被代理的行爲。
注意:數組
看一個簡單的實例app
var proxy = new Proxy({},{ get: function(target, key){ return 35; } }); console.log(proxy.time); //35 console.log(proxy.name); //35 console.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); //20 console.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, 咱們能夠定義如下攔截函數:this
攔截對象的讀取屬性。當 target 對象設置了 propKey 屬性的 get 函數時,receiver 綁定 get 函數的 this。返回值任意prototype
攔截對象的寫入屬性。返回一個布爾值代理
攔截 propKey in proxy 操做符,返回一個布爾值code
攔截 delete proxy[propKey] 操做符,返回一個布爾值orm
攔截 for(let i in proxy) 遍歷器,返回一個遍歷器
攔截 proxy.hasOwnProperty('foo'),返回一個布爾值
攔截 Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy),返回一個數組。該方法返回對象全部自身屬性,包括不可遍歷屬性,不包括 Symble屬性,可是Object.keys(proxy)
不該該包括不可遍歷屬性
攔截 Object.getOwnPropertyDescriptor(proxy, propKey),返回其屬性描述符
攔截 Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDesc),返回一個布爾值
攔截 Object.preventExtensions(proxy),返回一個布爾值
攔截 Object.getPrototypeOf(proxy),返回一個對象
攔截 Object.isExtensible(proxy),返回一個布爾值
攔截 Object.setPrototypeOf(proxy, proto),返回一個布爾值
攔截對 proxy 實例的函數操做,包括 proxy(...args),proxy.call(object, ...args),proxy.apply(object, args)
攔截用 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]); //10 console.log(arr[1]); //20 console.log(arr[2]); //undefined console.log(arr[-1]); //20 console.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); //13 obj.age = 20; console.log(obj.age); //20 console.log(obj._name); //undefined obj._hobby = "Coding"; //Error: Cannot define a property begin with "_" _name in key //false delete 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 對象有一下做用:
Object.defineProperty
遇到沒法定義屬性時會拋出錯誤,而 Reflect.defineProperty
會返回 falseReflect.has(obj,name)
替換 name in obj
代理在添加額外的功能時,利用 Reflect 保證了原始功能的實現。舉個例子:
var loggedObj = new Proxy({}, { get(target,propKey){ console.log(`getting ${target}.${propKey}`); //固然你最好把操做記錄到一個 log 中 return Reflect.get(target,propKey); } });
Reflect有如下方法:
等同於 ObjectgetOwnPropertyDescriptor(target, propKey)
等同於 Object.defineProperty(target,propKey,desc)
等同於 Object.getOwnPropertyNames(target)
等同於 Object.getPrototypeOf(target)
等同於 Object.setPrototypeOf(target, proto)
等同於 delete target.propKey
等同於 for ... in target
等同於 Object.freeze(target)
等同於 Object.seal(target)
等同於 Object.preventExtensions(target)
等同於 Object.isFrozen(target)
等同於 Object.isSealed(target)
等同於 Object.isExtensible(target)
等同於 propkey in object
等同於 target.hasOwnProperty(propKey)
遍歷獲得target自身全部屬性,包括不可枚舉屬性,不包括 Symbol 屬性
若是 propKey 是個讀取器,則讀取器中的 this 綁定到 receiver
var 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"
若是 propKey 是個讀取器,則讀取器中的 this 綁定到 receiver
等同於 Function.prototype.apply.call(target, thisArg, args)
即 thisArg.target(args)
等同於 new target(...args)
注意以上方法中,Reflect.set()
, Reflect.defineProperty()
, Reflect.freeze()
, Reflect.seal()
, Reflect.preventExtensions()
在成功時返回 true, 失敗時返回 false。對應的 Object 方法失敗時會拋出錯誤。