深度揭祕ES6代理Proxy
阿新 • • 發佈:2019-02-15
最近在部落格上看到關於ES6代理的文章都是一些關於如何使用Proxy的例子,很少有說明Proxy原理的文章,要知道只有真正掌握了一項技術的原理,才能夠寫出精妙絕倫的程式碼,所以我覺得有必要寫一篇關於深刻揭露ES6 Proxy的文章。
看完這篇文章你不會學到一些大型的使用Proxy的例子,但是你可以瞭解以下幾方面的內容:
- 你將知道代理是個什麼東西
- 你將知道所有代理物件可覆蓋的方法
- 一些代理物件使用的小場景
-
[[GetPrototypeOf]] ( )
獲取物件的原型時呼叫,在執行obj[__proto__]或Object.getPrototypeOf(obj)時呼叫。 -
[[SetPrototypeOf]] (V)
設定一個物件的原型時呼叫,在執行obj.prototype=otherObj或則Object.SetPrototypeOf(v)的時候呼叫 -
[[IsExtensible]] ( )
獲取物件的可擴充套件性時呼叫,執行Object.isExtensible(object)時被呼叫。 -
[[GetOwnProperty]] (P)
- [[PreventExtensions]]()
擴充套件一個不可擴充套件的物件時呼叫
-
[[DefineOwnProperty]] (P, Desc)
定義自有屬性時呼叫 -
[[HasProperty]](P)
檢測物件是否存在某個屬性時呼叫,如key in obj -
[[Get]] (P, Receiver)
獲取屬性時呼叫,如obj.key,obj[key]。 -
[[Set]] ( P, V, Receiver)
為物件的屬性賦值時呼叫,如obj.key=value或obj[key]=value。 -
[[Delete]] (P)
刪除某個屬性時呼叫 -
[[Enumerate]] ()
-
[[OwnPropertyKeys]] ( )
列舉物件的自有屬性時呼叫
- functionObj.[[Call]](thisValue, arguments)
呼叫一個函式時被呼叫,functionObj()或者x.method()。
- constructorObj.[[Construct]](arguments, newTarget)
使用new操作的時候呼叫,如new Date()。在整個 ES6 標準中,只要有可能,任何語法或物件相關的內建函式都是基於這14種內部方法構建的 。但是我們不必記住這些物件的內建屬性,我們更應關注是handler與之相對應的方法。 現在我們已經清楚了物件的14個內建方法,那麼我們再回到第一個問題:什麼是代理?我們可以這樣說,代理Proxy是一個建構函式,它可以接受兩個引數:目標物件(target) 與控制代碼物件(handler) ,返回一個代理物件Proxy,主要用於從外部控制對物件內部的訪問。
var target = {}, handler = {};
var proxy = new Proxy(target, handler);
那麼Proxy、target、handler這三者之間有什麼關係呢?Proxy的行為很簡單:將Proxy的所有內部方法轉發至target 。即呼叫Proxy的方法就會呼叫target上對應的方法。那麼handler又是用來幹嘛的?handler的方法可以覆寫任意代理的內部方法。 外界每次通過Proxy訪問target
物件的屬性時,就會經過 handler
物件,因此,我們可以通過重寫handler物件中的一些方法來做一些攔截的操作。以下是一個簡單的代理使用例子。
var person = {
name: "Jhon",
age: 23
};
var p = new Proxy(person,{
get: function(target, prop, receiver){
console.log("你訪問了person的屬性");
return target["name"];
}
});
console.log(p.age);
// 你訪問了person的屬性
// Jhon
你看,我們雖然訪問了age屬性,它卻輸出了額外的字串和屬性name的值,這就是攔截器的作用。
對應物件內建的14個方法,handler也有14個方法可以覆蓋,下面我們將會一一講解。
1、handler.get()
方法用於攔截物件的讀取屬性操作。
var p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
- target,目標物件
- property,被獲取的屬性名
- receiver,Proxy或者繼承Proxy的物件
- 可以返回任何值
- this繫結到handler物件上
- 如果要訪問的目標屬性是不可寫以及不可配置的,則返回的值必須與該目標屬性的值相同。
- 如果要訪問的目標屬性沒有配置訪問方法,即get方法是undefined的,則返回值必須為undefined。
handler.set()
方法用於攔截設定屬性值的操作
var p = new Proxy(target, {
set: function(target, property, value, receiver) {
}
});
- target,目標物件
- property,被設定的屬性名
- value,被設定的新值
- receiver,最初被呼叫的物件。通常是proxy本身,但handler的set方法也有可能在原型鏈上或以其他方式被間接地呼叫(因此不一定是proxy本身)
- this繫結到handler物件上
- 返回一個布林值,返回true代表此次設定屬性成功了,如果返回false且設定屬性操作發生在嚴格模式下,那麼會丟擲一個
TypeError
TypeError
:
- 若目標屬性是不可寫及不可配置的,則不能改變它的值
- 如果目標屬性沒有配置儲存方法,即set方法是undefined的,則不能設定它的值
- 在嚴格模式下,若set方法返回false,則會丟擲一個
TypeError
異常
handler.has()
方法可以看作是針對 in
操作的鉤子
var p = new Proxy(target, {
has: function(target, prop) {
}
});
- targe,t目標物件
- prop,需要檢查是否存在的屬性
- this繫結到handler物件上
- 返回一個boolean值
- 如果目標物件的某一屬性本身不可被配置,則該屬性不能夠被代理隱藏
- 如果目標物件為不可擴充套件物件,則該物件的屬性不能夠被代理隱藏
handler.getPrototypeOf()
是一個代理方法,當讀取代理物件的原型時,該方法就會被呼叫
var p = new Proxy(obj, {
getPrototypeOf(target) {
...
}
});
- target,被代理的目標物件
this
指向的是它所屬的處理器物件- 必須返回一個物件值或者返回
null
getPrototypeOf()
方法返回的不是物件也不是null
目標物件是不可擴充套件的,且
getPrototypeOf()
方法返回的原型不是目標物件本身的原型
handler.defineProperty()
用於攔截對物件的 Object.defineProperty()
操作
var p = new Proxy(target, {
defineProperty: function(target, property, descriptor) {
}
});
- target,目標物件
- property,待檢索其描述的屬性名
- descriptor,待定義或修改的屬性的描述符
this
繫結在 handler 物件上- 返回一個boolean值
- 如果目標物件不可擴充套件, 將不能新增屬性、
- 不能新增或者修改一個屬性為不可配置的,如果它不作為一個目標物件的不可配置的屬性存在的話
- 如果目標物件存在一個對應的可配置屬性,這個屬性可能不會是不可配置的
- 如果一個屬性在目標物件中存在對應的屬性,那麼
Object.defineProperty(target, prop, descriptor)
將不會丟擲異常 - 在嚴格模式下,
false
作為handler.defineProperty
方法的返回值的話將會丟擲TypeError
異
handler.deleteProperty()
方法用於攔截對物件屬性的 delete
操作
var p = new Proxy(target, {
deleteProperty: function(target, property) {
}
});
- target,目標物件
- property,待刪除的屬性名
this
被繫結在 handler上- 返回一個boolean值
- 如果目標物件的屬性是不可配置的,那麼該屬性不能被刪除
handler.setPrototypeOf()
用於攔截對物件的 Object.setPrototypeOf()
操作
var p = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
}
});
- target,目標物件
- prototype,待設定的屬性名或者null
this
被繫結在 handler上- 返回一個boolean值
- 如果目標物件是不可擴充套件的,則原型的引數必須和Object.getPrototypeOf(target)相同
var p = new Proxy(target, {
getOwnPropertyDescriptor: function(target, prop) {
}
});
- target,目標物件
- prop,返回屬性名的描述符
- this繫結到處理函式
- 必須返回一個 物件 或 u
ndefined
getOwnPropertyDescriptor
必須返回一個 object 或 undefined
如果屬性作為目標物件的不可配置的屬性存在,則該屬性無法報告為不存在
如果屬性作為目標物件的屬性存在,並且目標物件不可擴充套件,則該屬性無法報告為不存在
如果屬性不存在作為目標物件的屬性,並且目標物件不可擴充套件,則不能將其報告為存在
屬性不能被報告為不可配置,如果它不作為目標物件的自身屬性存在,或者作為目標物件的可配置的屬性存在
Object.getOwnPropertyDescriptor(target)的結果可以使用 Object.defineProperty 應用於目標物件,也不會丟擲異常
var p = new Proxy(target, {
ownKeys: function(target) {
}
});
- target,目標物件
- this繫結到處理函式
- 返回一個數組
- 返回值必須是一個數組
- 陣列的每一個元素必須是字串或者Symbol
- 陣列必須包含目標物件的所有非可配置屬性的鍵
- 如果目標物件不可擴充套件,則陣列必須包含目標物件自身屬性的所有鍵,並且沒有其他值
var p = new Proxy(target, {
isExtensible: function(target) {
}
});
- target,目標物件
- this繫結到處理函式
- 返回一個boolean值
Object.isExtensible(proxy)
必須返回和Object.isExtensible(target)相同的值
handler.apply()
方法用於攔截函式的呼叫
var p = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {
}
});
- target,目標物件(函式)
- thisArg,被呼叫時的上下文物件
- argumentsList,被呼叫時的引數列表
- this繫結到handler物件
- 可以返回任何值
- 無
handler.construct()用於來接new操作
var p = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
}
});
- target,目標物件
- argumensList,構造器引數列表
- newTarget,最初呼叫的建構函式
- this繫結到handler
- 返回一個物件
- 返回值必須是一個物件
var p = new Proxy(target, {
preventExtensions: function(target) {
}
});
- target,目標物件
- this繫結到handler物件
- 返回一個boolean值
- Object.isExtensible(proxy)為假,則Object.preventExtensions(proxy)只能返回true