Proxy 用於修改某些操作的默認行爲,等同於在語言層面做出修改,所以屬於一種“元編程”(meta programming),即對編程語言進行編程。
Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來“代理”某些操作,可以譯爲“代理器”。
ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
var proxy = new Proxy(target, handler);
Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數
的寫法。其中,new Proxy()
表示生成一個Proxy
實例,target
參數表示所要攔截的目標對象,handler
參數也是一個對象,用來定製攔截行爲
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
Proxy支持的攔截對象一共有13種
- 1、
get(target, propKey, receiver)
:攔截對象屬性的讀取 - 2、
set(target, propKey, value, receiver)
:攔截對象屬性的設置,返回一個布爾值 - 3、
has(target, propKey)
:攔截propKey in proxy的操作,返回一個布爾值 - 4、
deleteProperty(target, propKey)
:攔截delete proxy[propKey]的操作,返回一個布爾值。 - 5、
ownKeys(target)
:攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in
循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()
的返回結果僅包括目標對象自身的可遍歷屬性 - 6、
getOwnPropertyDescriptor(target, propKey)
:攔截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回屬性的描述對象 - 7、
defineProperty(target, propKey, propDesc)
:攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs)
,返回一個布爾值 - 8、
preventExtensions(target)
:攔截Object.preventExtensions(proxy)
,返回一個布爾值 - 9、
getPrototypeOf(target)
:攔截Object.getPrototypeOf(proxy)
,返回一個對象 - 10、
isExtensible(target)
:攔截Object.isExtensible(proxy)
,返回一個布爾值 - 11、
setPrototypeOf(target, proto)
:攔截Object.setPrototypeOf(proxy, proto)
,返回一個布爾值。如果目標對象是函數,那麼還有兩種額外操作可以攔截 - 12、
apply(target, object, args)
:攔截 Proxy 實例作爲函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
。 - 13、
construct(target, args)
:攔截 Proxy 實例作爲構造函數調用的操作,比如new proxy(...args)
1、get()
get方法用於攔截某個屬性的讀取操作,可以接受三個參數,依次爲目標對象、屬性名和 proxy 實例本身(嚴格地說,是操作行爲所針對的對象),其中最後一個參數可選
var person = {
name: "張三"
};
var proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name // "張三"
proxy.age // 拋出一個錯誤
上面代碼表示,如果訪問目標對象不存在的屬性,會拋出一個錯誤。如果沒有這個攔截函數,訪問不存在的屬性,只會返回undefined
。
get
方法可以繼承
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo // "GET foo"
利用 Proxy,可以將讀取屬性的操作(get),轉變爲執行某個函數,從而實現屬性的鏈式操作。
var pipe = (function () {
return function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
}());
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
2、set()
set方法用來攔截某個屬性的賦值操作,可以接受四個參數,依次爲目標對象、屬性名、屬性值和 Proxy 實例本身,其中最後一個參數可選
假定Person對象有一個age屬性,該屬性應該是一個不大於 200 的整數,那麼可以使用Proxy保證age的屬性值符合要求
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 對於滿足條件的 age 屬性以及其他屬性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 報錯
person.age = 300 // 報錯
3、apply()
apply方法攔截函數的調用、call和apply操作。
apply方法可以接受三個參數,分別是目標對象、目標對象的上下文對象(this)和目標對象的參數數組。
var target = function () { return 'I am the target'; };
var handler = {
apply: function () {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p()
上面代碼中,變量p是 Proxy 的實例,當它作爲函數調用時(p()),就會被apply方法攔截,返回一個字符串。
4、has()
has方法用來攔截HasProperty操作,即判斷對象是否具有某個屬性時,這個方法會生效。典型的操作就是in運算符。
has方法可以接受兩個參數,分別是目標對象、需查詢的屬性名
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false