1. 概述 Proxy 用於修改某些操作的預設行為,等同於在語言層面做出修改,所以屬於一種『元編程』即對編程語言進行編程。 1.1 理解 Proxy 是在目標對象之前架設一層『攔截』,外部對對象的訪問,都需要經過該層攔截。因此在攔截中對外界的訪問進行過濾和改寫。 在Es6 中 提供了原生的 Prox ...
1. 概述
Proxy 用於修改某些操作的預設行為,等同於在語言層面做出修改,所以屬於一種『元編程』即對編程語言進行編程。
1.1 理解
Proxy 是在目標對象之前架設一層『攔截』,外部對對象的訪問,都需要經過該層攔截。因此在攔截中對外界的訪問進行過濾和改寫。
在Es6 中 提供了原生的 Proxy 構造函數,可以用來生成 Proxy實例。
let proxy = new Proxy(target, handler)
Proxy 對象的所有用法,都是上面的形式,不同的只是handler參數的寫法。其中new Proxy() 表示生成一個 Proxy實例,target 參數表示所有攔截的目標對象, handler 參數也是一個對象,用來定製攔截行為。如下:
let proxy = new Proxy({}, {
get: function(target, property) {
return 35
}
})
proxy.time // 35
proxy.name // 35
proxy.title // 35
解讀:
在上面中,作為構造函數,Proxy接受兩個參數。第一個參數即所要代理的目標對象,如果沒有 Proxy的介入,操作原來要訪問的就是這個對象。第二個參數是一個配置對象,用來對每個代理對象的操作,提供具體的函數和攔截操作。上述代碼中有一個 get 函數,用來攔截對目標對象屬性的訪問請求。
另外,要使 Proxy起作用,必須針對 Proxy 實例進行操作,而不是針對目標對象進行操作。
如果 handler 沒有設置任何攔截,那就等同於直接通向原對象。如下:
let target = {}
let handler = {}
let proxy = new Proxy(target, handler)
proxy.a = 'b'
target.a = 'b'
對於上面的例子,我們可以講Proxy對象,設置到 object.proxy屬性,從而可以在object對象上調用。
let object = {proxy: new Proxy(target, handler)}
Proxy 實例也可以作為其他對象的原型對象。
let proxy = new Proxy({}, {
get: function(target, property) {
retrun 35
}
})
let obj = Object.create(proxy)
obj.time // 35
另外同一個攔截器,可以設置多個攔截操作。
常用的Proxy支持的攔截操作如下:
- get(target, propKey, receiver): 攔截對象屬性的讀取,比如 proxy.foo 和 proxy['foo']
- set(target, propKey, value, receiver): 攔截對象屬性的設置,比如 proxy.foo = v 或 proxy['foo'] = v, 返回一個布爾值
- has(target, proKey): 攔截propKey in proxy的操作,返回一個布爾值
- apply(target, object, args): 攔截Proxy 實例作為函數調用的操作,比如 proxy(...args)、proxy.call(object, ...args) 、 proxy.apply(...)。
- deleteProperty(target, proKey): 攔截 delete proxy[propKey]的操作,返回一個布爾值
- ownKeys(target): 攔截 Object.getOwnProPertyNames(proxy),返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys() 的返回結果僅包括目標對象自身的可遍歷屬性。
1.2 get()
get 方法用於攔截某個屬性的讀取操作,可以接受三個參數,依次為目標對象、屬性名和proxy實例本身,最後一個參數可選。
let p = {
name: '李四'
}
let proxy = new Proxy(p, {
get: function(target, property) {
if (property in target) {
retrun target[property]
} else {
console.log('報錯')
}
}
})
proxy.name // '李四'
proxy.age // '報錯'
註意點
- get方法可以繼承
- get方法可以進行鏈式操作
1.3 set
set 方法用來攔截某個屬性的賦值操作,可以接受四個參數,依次為目標對象、屬性名、屬性值和Proxy實例本身,最後一個可選
let v = {
set: function(obj, prop, value) {
if (prop === 'age') {
if(!Number.isInteger(value)) {
console.log('報錯')
}
if(value > 200) {
console.log('成功')
}
obj[prop] = value
}
}
}
let p = new Proxy({}, v)
p.age = 100
p.age // 100
p.age = 'n'
p.age // 報錯
利用set方法,可以數據綁定,即每當對象發生變化時,會自動更新Dom
如果目標對象自身的某個屬性,不可寫且不可配置,那麼Set 方法將不起作用。
1.4 apply()
apply 方法攔截函數的調用、call 和 apply操作。它可以接受三個參數,分別時目標對象、目標對象的上下文對象(this)和目標對象的參數數組。
let h = {
apply(target, ctx, args) {
return Reflect.apply(...arguments)
}
}
let target = function() {return 'haha')
let h = {
apply: function() {
return 'heihei'
}
}
let p = new Proxy(taraget, h)
p() // 'heihei'
1.5 has
has 方法用來攔截 HasProperty 操作, 即判斷對象是否具有某個屬性時,這個方法會生效。
has 方法可以接受兩個參數,分別時目標對象、需要查詢的屬性名。
註意:has 方法攔截的時 HasProperty操作,而不是HasOwnProperty操作,即has 方法不判斷一個屬性是對象自身的屬性,還是繼承的屬性。
1.6 construct
construct 方法用於攔截 new命令,下麵是攔截對象的寫法
let h = {
construct(target, args, newTarget) {
retrun new target(...args)
}
}
target: 目標對象
args: 構造函數的參數對象
newTarget: 創造實例對象時,new命令作用的構造函數
1.7 deleteProperty()
deleteProperty 方法用於攔截 delete 操作,如果這個方法拋出錯誤或者返回false,當前屬性就無法被delete命令刪除。
2. this問題
雖然 Proxy 可以代理針對目標對象的訪問,但它不是目標對象的透明代理,即不做任何攔截的情況下,也無法保證目標對象的行為一致。主要原因就是在Proxy代理的情況下,目標對象內部的this關鍵字會指向Proxy代理