一.Proxy基礎
1. 什么是Proxy?
Proxy是一個構造函數,可以通過它生成一個Proxy實例。
const proxy = new Proxy(target, handler); // target: 目標對象,待操作對象(可以是普通對象,數組,函數,proxy實例對象) // handler: 一個對象,最多可包含13個操作方法,也可以是空對象
2. Proxy的作用
1. Proxy是一個ES6語法,它的作用主要是通過handler對象中的攔截方法攔截目標對象target的
某些操作(如讀取,賦值,函數調用,new等),然后過濾或者改寫這些操作的默認行為,
或者只是在完成這些操作的默認行為的基礎上,增加一些副作用(如打印前置log等)。
2. 生成的實例對象是針對target對象的“攔截器”。也可以叫做“代理器”。
3. 然后必須通過操作proxy,即“攔截器”(攔截器對象本身性質和目標對象target一樣,比如:target是函數,
那么proxy也是函數)才能觸發攔截方法,來完成對目標對象的操作和訪問。
4. 如果handler是個空對象({}),那么操作攔截器相當於直接操作目標對象target。
3. Proxy構造函數的特征
1. Proxy函數沒有prototype屬性,所以也就不能使用instanceof判斷是否是Proxy實例
2. Proxy實例的數據類型和target數據類型一致。
var proxy1 = new Proxy([], {}); proxy1 instanceof Array; // true var proxy2 = new Proxy(function(){}, {}); typeof proxy2; //"function"
二. Proxy攔截器handler方法
一共有13個攔截方法(對應Reflect的13個方法),可以大體分為兩個部分。
1. 新的方法名
返回值是布爾值的方法有:
1. has(target, propKey)
作用: 攔截判斷target對象是否含有屬性propKey的操作
攔截操作: propKey in proxy; 不包含for...in循環
對應Reflect: Reflect.has(target, propKey)
語法:
const handler = { has(target, propKey){ // ... //默認操作 return propKey in target; } } const proxy = new Proxy(target, handler)
示例: 過濾某些私有屬性
const handler = { has(target, propKey) { if (propKey[0] === '_') { return false; } return propKey in target; } } const target = {_myprop: 1, a: 2, c:3}; const proxy = new Proxy(target, handler); '_myprop' in proxy; // false
特殊情況: 目標target是不可擴展或者某個屬性不可配置,只能返回默認行為結果;否則報錯
var obj = { a: 10 }; Object.preventExtensions(obj); var p = new Proxy(obj, { has: function(target, prop) { return false; //只能是return prop in target; } }); 'a' in p; //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
2. deleteProperty(target, propKey)
返回:嚴格模式下只有返回true, 否則報錯
作用: 攔截刪除target對象的propKey屬性的操作
攔截操作: delete proxy[propKey]
語法:
const handler = { deleteProperty(target, propKey){ // ... //默認操作; 操作成功並返回true;操作失敗報錯 const bool = Reflect.deleteProperty(...arguments); if (bool) { return bool; } else { throw new Error("delete failed"); } } } const proxy = new Proxy(target, handler)
示例:
var obj = { a: 10 }; var p = new Proxy(obj, { deleteProperty(target, prop) { console.log('delete propName ',prop); return delete target[prop]; // 嚴格模式下操作成功必須返回true;否則報錯 } }); delete p.a; console.log(obj); // 運行結果如下: 'delete propName a' {}
特殊情況: 屬性是不可配置屬性時,不能刪除; 但是對象不可擴展的時候,可以刪除屬性。
var obj = { a: 10 }; Object.defineProperty(obj, 'b', { value: 2, configurable: false }); var p = new Proxy(obj, { deleteProperty(target, prop) { return delete target[prop]; } }); delete p.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object> console.log(obj);
3. ownKeys(target)
返回: 數組(數組元素必須是字符或者Symbol,其他類型報錯)
作用: 攔截獲取鍵值的操作
攔截操作: Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
for...in...循環
語法:
最后取到的結果不是return的值,而是會自動過濾
const handler = { ownKeys(target) {
// 所有的keys;也可以是其他的數組 return Reflect.ownKeys(target); } }
示例:
var obj = { a: 10, [Symbol.for('foo')]: 2 }; Object.defineProperty(obj, 'c', { value: 3, enumerable: false }) var p = new Proxy(obj, { ownKeys(target) { return [...Reflect.ownKeys(target), 'b', Symbol.for('bar')]; } }); const keys = Object.keys(p); // ['a'] // 自動過濾掉Symbol/非自身/不可遍歷的屬性 for(let prop in p) { // 和Object.keys()過濾性質一樣,只返回target本身的可遍歷屬性 console.log('prop-',prop); // prop-a } const ownNames = Object.getOwnPropertyNames(p);// ['a', 'c', 'b'] // 只返回攔截器返回的非Symbol的屬性,不管是不是target上的屬性 const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)] // 只返回攔截器返回的Symbol的屬性,不管是不是target上的屬性 const ownKeys = Reflect.ownKeys(p);// ['a','c',Symbol(foo),'b',Symbol(bar)] // 返回攔截器返回的所有值
特殊情況:
1)如果某個屬性是不可配置的,那么該屬性在攔截器中必須被返回,否則報錯
2)如果target對象是不可擴展的,那么攔截器返回的數組必須是操作的默認返回結果。
即必須是被攔截的操作的默認行為,如getOwnPropertyNames()
4. apply(target, thisArg, args)--target是函數
返回:函數執行結果
作用: 攔截proxy作為函數調用時的操作
攔截操作:proxy()
proxy.call(obj, ...args)
proxy.apply(obj, [...args])
Reflect.apply(proxy, thisArg, args)
語法:
const handler = { apply(target, contextThis/*上下文對象this*/, args/*參數數組*/) { return Reflect.apply(...arguments); } }
示例:
const handler = { apply(target, contextThis/*上下文對象this*/, args/*參數數組*/) { console.log('---apply攔截器---',contextThis,'-',args); return Reflect.apply(...arguments)+'end'; } } let target = function(a,b) { return a+b; } const proxy = new Proxy(target, handler); console.log('proxy-->',proxy(5,6)); console.log('proxy.call-->',proxy.call(null, 5, 6)); console.log('proxy.apply-->',proxy.apply(null, [5,6])); console.log('Reflect-->',Reflect.apply(proxy, null, [5,6])); // 運行結果如下: /* ---apply攔截器---undefined-[5,6] proxy-->11end ---apply攔截器---null-[5,6] proxy.call-->11end ---apply攔截器---null-[5,6] proxy.apply-->11end ---apply攔截器---null-[5,6] Reflect-->11end */
5. construct(target, args, newTarget)--target是構造函數
返回: 實例對象, 不是對象會報錯
作用: 攔截new命令
攔截操作: new proxy(...args)
語法:
const handler = { construct(target, args, newTarget){// args是參數數組, newTarget是生成的proxy實例 console.log('----攔截new----',args,'-', newTarget === proxy); return Reflect.construct(target, args);// 默認行為,也可以return {a: b},只要是對象就可以 } } const target = function(a,b) {}; var proxy = new Proxy(target, handler); console.log('proxy type-->', typeof proxy); const result = new proxy(1,2); // 觸發攔截器 console.log('result type-->',typeof result)
// 運行結果如下: proxy type-->function ----攔截new----[1,2]-true result type-->object
2. 方法名和對象原有方法名一樣
6. get(target, propKey, receiver)
返回: 返回讀取的屬性
作用:攔截對象屬性的讀取
攔截操作:proxy[propKey]或者點運算符
語法:
const handler = { get(target, propKey, receiver) {// receiver是proxy實例 return Reflect.get(target, propKey); } }
示例: 實現函數的鏈式操作
const funsObj = { double(n) {return n*2}, square(n) {return n**2} } var pipe = (value) => { const callStack=[]; return new Proxy({}, { get(target, propKey, receiver) { console.log(propKey,callStack); if (propKey === 'get') { return callStack.reduce((val, fn) => { return fn(val); }, value) } else { callStack.push(funsObj[propKey]); } return receiver; //返回proxy實例才能實現鏈式調用 } }) } pipe(3).double.square.get; //36
get方法可以繼承,但是receiver的值會是直接觸發的那個對象。
const proxy= new Proxy({}, { get(target, propKey, receiver) { return receiver; } }) const p = Object.create(proxy); console.log(p.a === p); // true p.a返回receiver
特殊情況:如果對象的屬性writable和configurable同時為false, 則攔截器只能返回默認行為
const target = {}; Object.defineProperty(target, 'a', { value: 1, writable: false, configurable: false }) const proxy = new Proxy(target, { get(target,propKey) { return 2; //應該return 1;不能返回其他值,否則報錯 } }) proxy.a; // Uncaught TypeError
7.set(target,propKey, value,receiver)
返回:嚴格模式下返回true操作成功;否則失敗,報錯
作用: 攔截對象的屬性賦值操作
攔截操作: proxy[propkey] = value
語法:
const proxy = new Proxy({}, { set(target, propKey, value,receiver) {// receiver時proxy實例 const bool = Reflect.set(...arguments); if (bool){ return //!!!嚴格模式下操作成功必須返回true,否則報錯;非嚴格模式下可以省略 //當Reflect.set傳入的參數有receiver且屬性writable=true時,會在receiver在定義屬性,會觸發defineProperty攔截 } else { throw new Error("操作失敗") } }, defineProperty(target, propKey, propDesc) { console.log('defineProperty') } })
set方法也可以繼承,receiver也是最終調用的那個實例,和get方法一樣。參照get的方法。
設置 target[propKey] = receiver;
當對象的屬性writable為false時,該屬性不能在攔截器中被修改;
const obj = {}; Object.defineProperty(obj, 'foo', { value: 'bar', writable: false, configurable: true, }); const handler = { set: function(obj, prop, value, receiver) { return Reflect.set(...arguments); }, }; const proxy = new Proxy(obj, handler); proxy.foo = 'baz'; console.log(obj); // {foo: bar} 說明修改失敗
8. defineProperty(target, propKey,PropDesc)
返回: 嚴格模式下操作成功必須返回true;否則報錯
作用:攔截定義屬性或者重新定義屬性操作
攔截操作: Object.defineProperty(proxy, propKey,propDesc)
Reflect.defineProperty(proxy, propKey,propDesc)
語法兼示例:
const proxy = new Proxy({}, { defineProperty(target, propKey, propDescriptor) {// 最后一個參數是屬性描述對象 const bool = Reflect.defineProperty(...arguments); if (bool) { return bool;// !!嚴格模式下操作成功必須返回true,否則報錯 } else { throw new Error("定義屬性失敗") } } }) Object.defineProperty(proxy, 'a', {value:5})
特殊情況:
如果對象是不可擴展的(preventExtensible(obj)),則不能添加屬性;
如果對象的某屬性writable和configurable同時為false, 則不能重新定義該屬性的值;
如果上面的屬性有其中一個是true,可以重新定義該屬性的值。
9. getPrototypeOf(target)
返回: 返回對象或者null,否則報錯
作用:攔截獲取原型對象的操作
攔截操作:Object.getPrototypeOf(proxy)
proxy.__proto__
Object.isPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
instanceof
語法:
var p = new Proxy({}, { getPrototypeOf(target) { return Reflect.getPrototypeOf(target); } });
示例:
var proxy = new Proxy({}, { getPrototypeOf(target) { return {a:1} } }); Object.getPrototypeOf(proxy); // {a:1}
特殊情況:
如果目標對象是不可擴展的,那么只能返回默認的原型對象。
10.setPrototypeOf(target, proto)
返回:嚴格模式下返回true,否則報錯;只能是布爾值
作用:攔截設置原型對象的操作
攔截操作:Object.setPrototypeOf(proxy, proto)
proxy.__proto__
Reflect.setPropertyOf(proxy,proto)
語法:
var proxy = new Proxy({}, { setPrototypeOf(target, proto) { const bool = Reflect.setPrototypeOf(...arguments); if (bool) { return bool; // !!!嚴格模式下操作成功必須返回true;否則報錯 } else { throw new Error("設置原型對象失敗"); } } })
特殊情況:
如果對象不可擴展,只能進行默認行為。不能修改。
11. isExtensible(target)---只能返回默認行為結果
返回:布爾值
作用: 攔截是否可擴展方法的調用
攔截操作: Object.isExtensible(proxy)
Reflect.isExtensible(proxy)
語法:
const proxy = new Proxy({}, { isExtensible(target) { return Reflect.isExtensible(target); //!!!返回結果只能是這個;即proxy和target的可擴展性必須是一致的 } })
12. preventExtensible(target)--遵循默認行為
返回:嚴格模式下返回true,否則報錯;
攔截操作:Object.preventExtensible(proxy)
Reflect.preventExtentsible(proxy)
語法:
const proxy = new Proxy({}, { preventExtensions(target) { return Reflect.preventExtensions(target); //嚴格模式下,必須存在 } })
13. getOwnPropertyDescriptor(target, propKey)
返回: 對象或者undefined
攔截操作: Object.getOwnPropertyDescriptor(proxy)
Reflect.getOwnPropertyDescriptor(proxy)
語法:
const proxy = new Proxy({}, { getOwnPropertyDescriptor(target, propKey) { return Reflect.getOwnPropertyDescriptor(target,propKey); } })
三. 靜態方法-Proxy.revocable()
作用: 返回一個可取消的proxy實例
語法:
const {proxy, revoke} = Proxy.revocable(target,handler);
// proxy是實例
// revoke是個方法;直接調用后取消proxy實例
示例:
const {proxy, revoke} = Proxy.revocable({},{});
proxy.a=5;
revoke();
proxy.a // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
應用:
用於一個只能訪問一次的代理器
四. this指向
攔截器方法中的this指向proxy實例
五.Proxy應用
觀察者模式:通過攔截對象的賦值操作,實時調用觀察函數。
如: mobx中的observable, observe的實現;通過函數觀察對象,並實時作出反應
const obj = {name: 'lyra', age: 18};//被觀察者--使用Proxy
const fn1 = () => {
obj.age = 16;
console.log(`Hello, ${obj.name}, ${obj.age}`);
}
const fn2 = () => console.log('there must be sth changed');
const observeFuncs = new Set();
const observe = (fn) => observeFuncs.add(fn);
const observable = function(obj) {
return new Proxy(obj, {
set(target, prop, value) {
observeFuncs.forEach(observer => observer()); //被觀察者屬性被賦值時,觸發觀察者函數調用
return Reflect.set(...arguments); //默認行為
}
})
}
const objProxy = observable(obj);//指定被觀察者對象
observe(fn1); //指定觀察者函數
observe(fn2);
objProxy.name = 'lyraLee';
// 運行結果如下:
Hello, lyraLee, 16
there must be sth changed
