Proxy 用于修改某些操作的預設行為,等于是在語言層面做出了修改,也就是對程式設計語言進行改動。具體來說,Proxy就是一種機制,用來攔截外界對目标對象的通路,可以對這些通路進行過濾或者改寫,是以Proxy更像是目标對象的代理器。
1、ES6 原生提供Proxy構造函數,可以用來生成proxy執行個體:
(1)執行個體
let proxy = new Proxy(target, handler);
接收兩個參數:
target 是要代理的目标對象;
handler 也是一個對象,用來定義攔截的具體行為;如果攔截具有多個操作,就可以這樣定義handler {fn, ….}
(2)Proxy 攔截操作彙總,共13個:
-
:攔截對象屬性的讀取,比如get(target, propKey, receiver)
;proxy.foo
-
:攔截對象屬性的設定,set(target, propKey, value, receiver)
,傳回一個布爾值;比如proxy.foo = v
-
:攔截has(target, propKey)
的操作,傳回一個布爾值;propKey in proxy
-
:攔截deleteProperty(target, propKey)
的操作,傳回一個布爾值;delete proxy[propKey]
-
:攔截ownKeys(target)
、Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
循環,傳回一個數組。該方法傳回目标對象所有自身的屬性的屬性名,而for...in
的傳回結果僅包括目标對象自身的可周遊屬性。Object.keys()
-
: 攔截getOwnPropertyDescriptor(target, propKey)
,傳回屬性的描述對象。Object.getOwnPropertyDescriptor(proxy, propKey)
-
:攔截defineProperty(target, propKey, propDesc)
、Object.defineProperty(proxy, propKey, propDesc)
,傳回一個布爾值。Object.defineProperties(proxy, propDescs)
-
:攔截preventExtensions(target)
,傳回一個布爾值。Object.preventExtensions(proxy)
-
:攔截getPrototypeOf(target)
,傳回一個對象。Object.getPrototypeOf(proxy)
-
:攔截isExtensible(target)
,傳回一個布爾值。Object.isExtensible(proxy)
-
:攔截setPrototypeOf(target, proto)
,傳回一個布爾值。如果目标對象是函數,那麼還有兩種額外操作可以攔截。Object.setPrototypeOf(proxy, proto)
-
:攔截 Proxy 執行個體作為函數調用的操作,比如apply(target, object, args)
。proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
-
:攔截 Proxy 執行個體作為構造函數調用的操作,比如construct(target, args)
。new proxy(...args)
(3)Proxy常用攔截方法詳解(暫時就用到這兩個,過後再補):
get ( ): 如果某個屬性不可寫或者不可配置,則該屬性不能被通路,set()也同理;
如上所述,這個get() 方法用來攔截對目标對象的屬性通路;還是上執行個體吧:
let idiot= {
name: "Troyes",
age:
};
let proxy = new Proxy(idiot, {
get (target, property) {
//我天,有個return怎麼就用不了三元操作符?
if (property in target){
return target[property];
}else{
throw new ReferenceError("the property you want to get:"+ property +" does not exist!");
}
//老衲有妙招:就是有點煩,flag無論真假,都是return出去,容我以後再來想想能不能優化
//return (property in target) ? (target[property]) : ( "the property you want to get:"+ property +" does not exist!");
}
});
console.log(proxy.name, proxy.age); //Troyes
console.log(proxy.job); //ReferenceError: the property you want to get:job does not exist!
set ( ):
這個方法用于攔截對某個屬性的指派操作,廢話不都說,上執行個體:
let checker = {
set (target, prop, val) {
//驗證設定的特定的屬性值符不符合要求
if (prop=== 'age') {
if (Number.isInteger(val)) {
if (val>) {
throw new RangeError("Are you f**king kiding me?");
}
}else{
throw new TypeError("The age is not a integer.");
}
}
//無要求的屬性值,符合要求的age都直接存起來
target[prop] = val;
}
};
let Person = new Proxy({}, checker);
Person.age = ;
console.log(Person.age); //35
Person.age = "123d"; //TypeError: The age is not a integer.
Person.age = ; //RangeError: Are you f**king kiding me?
Person.name = "troyes";
console.log(Person.name); //troyes
(4)關于 This
用了
Proxy
之後,proxy代理的 this 并非指向目标對象,而是指向自身proxy。是以這裡就會有坑:某些原生對象的 this 指向如果不對,是無法通路到他的内部屬性的;(解決方法下面有講到)
let a = {
toConsole () {
if (this === proxy) {
console.log("the 'this' of a change its pointing to proxy.");
}else if (this === a) {
console.log("waiting to chang its pointing.");
}
}
};
let proxy = null;
a.toConsole(); //waiting to chang its pointing.
proxy = new Proxy( a, {});
a.toConsole(); //waiting to chang its pointing. 在a中,a的this沒變,還是a;
proxy.toConsole (); //the 'this' of a change its pointing to proxy. 但是在proxy中,this卻已經指向了proxy;
有些原生對象的内部屬性的this不對就會報錯;那麼知道坑在這裡,怎麼解決呢?
答案就是通過綁定this,寫 handler 這個參數的時候,通過 bind(target) 直接把this 綁定到原生對象 target上 ;
let target = new Date();
let handler = {
get (target, prop) {
if (prop === 'getTime') {
//直接把this綁定到target上
return target.getTime.bind(target);
}
return Reflect.get(target, prop);
}
};
let proxy = new Proxy(target, handler);
let nowTime = proxy.getTime();
console.log(nowTime); //1536074540607
//加上這句,直接報錯
proxy.getDay(); //TypeError: this is not a Date object.
寫到這裡,索性再來說說
bind(obj)
這個方法吧;
正如上面說的,調用這個方法就會直接把 this 直接綁定到 obj 上;
var module = {
x: ,
getX: function () {
return this.x;
}
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope, the this is pointing to window, so window.x is undefined
// expected output: undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
//bind the this to module
// expected output: