Proxy是什麼
起首,我們要清晰,Proxy
是什麼意義,這個單詞翻譯過來,就是 代辦。
可以理解為,有一個很火的明星,開通了一個微博賬號,這個賬號非常活潑,復興粉絲、隨處點贊之類的,但可以並非真的由本人在保護的。
而是在背地有一個其他人 or 團隊來運營,我們就可以稱他們為代辦人,由於他們宣布的微博就代表了明星本人的意義。
P.S. 強行舉例子,由於本人不追星,只是猜想可以會有如許的運營團隊
這個代入到JavaScript
當中來,就可以理解為對對象
或許函數
的代辦操縱。
JavaScript中的Proxy
Proxy是ES6中供應的新的API,可以用來定義對象種種基礎操縱的自定義行動
(在文檔中被稱為traps
,我以為可以理解為一個針對對象種種行動的鈎子)
拿它可以做許多有意義的事變,在我們須要對一些對象的行動舉行掌握時將變得非常有用。
Proxy的語法
建立一個Proxy
的實例須要傳入兩個參數
-
target
要被代辦的對象,可以是一個object
或許function
-
handlers
對該代辦對象的種種操縱行動處置懲罰
let target = {}
let handlers = {} // do nothing
let proxy = new Proxy(target, handlers)
proxy.a = 123
console.log(target.a) // 123
在第二個參數為空對象的狀況下,基礎可以理解為是對第一個參數做的一次淺拷貝
(Proxy必需是淺拷貝,假如是深拷貝則會失去了代辦的意義)
Traps(種種行動的代辦)
就像上邊的示例代碼一樣,假如沒有定義對應的trap
,則不會起任何作用,相當於直接操縱了target
。
當我們寫了某個trap
今後,在做對應的行動時,就會觸發我們的回調函數,由我們來掌握被代辦對象的行動。
最常常運用的兩個trap
應當就是get
和set
了。
從前JavaScript
有着在定義對象時針對某個屬性舉行設置getter
、setter
:
let obj = {
_age: 18,
get age () {
return `I'm ${this._age} years old`
},
set age (val) {
this._age = Number(val)
}
}
console.log(obj.age) // I'm 18 years old
obj.age = 19
console.log(obj.age) // I'm 19 years old
就像這段代碼形貌的一樣,我們設置了一個屬性_age
,然後又設置了一個get age
和set age
。
然後我們可以直接挪用obj.age
來獵取一個返回值,也可以對其舉行賦值。
這麼做有幾個瑕玷:
- 針對每個要代辦的屬性都要編寫對應的
getter
、setter
。 - 必需還要存在一個存儲實在值的
key
(假如我們直接在getter
裡邊挪用this.age
則會湧現客棧溢出的狀況,由於不管什麼時候挪用this.age
舉行取值都邑觸發getter
)。
Proxy
很好的處理了這兩個題目:
let target = { age: 18, name: 'Niko Bellic' }
let handlers = {
get (target, property) {
return `${property}: ${target[property]}`
},
set (target, property, value) {
target[property] = value
}
}
let proxy = new Proxy(target, handlers)
proxy.age = 19
console.log(target.age, proxy.age) // 19, age : 19
console.log(target.name, proxy.name) // Niko Bellic, name: Niko Bellic
我們經由歷程建立get
、set
兩個trap
來一致管理一切的操縱,可以看到,在修正proxy
的同時,target
的內容也被修正,而且我們對proxy
的行動舉行了一些特別的處置懲罰。
而且我們無需分外的用一個key
來存儲實在的值,由於我們在trap
內部操縱的是target
對象,而不是proxy
對象。
拿Proxy來做些什麼
由於在運用了Proxy
后,對象的行動基礎上都是可控的,所以我們能拿來做一些之前完成起來比較複雜的事變。
在下邊列出了幾個簡樸的實用場景。
處理對象屬性為undefined的題目
在一些層級比較深的對象屬性獵取中,怎樣處置懲罰undefined
一直是一個痛楚的歷程,假如我們用Proxy
可以很好的兼容這類狀況。
(() => {
let target = {}
let handlers = {
get: (target, property) => {
target[property] = (property in target) ? target[property] : {}
if (typeof target[property] === 'object') {
return new Proxy(target[property], handlers)
}
return target[property]
}
}
let proxy = new Proxy(target, handlers)
console.log('z' in proxy.x.y) // false (實在這一步已針對`target`建立了一個x.y的屬性)
proxy.x.y.z = 'hello'
console.log('z' in proxy.x.y) // true
console.log(target.x.y.z) // hello
})()
我們代辦了get
,並在裡邊舉行邏輯處置懲罰,假如我們要舉行get
的值來自一個不存在的key
,則我們會在target
中建立對應個這個key
,然後返回一個針對這個key
的代辦對象。
如許就可以保證我們的取值操縱肯定不會拋出can not get xxx from undefined
然則這會有一個小瑕玷,就是假如你確切要推斷這個key
是不是存在只可以經由歷程in
操縱符來推斷,而不可以直接經由歷程get
來推斷。
一般函數與組織函數的兼容處置懲罰
假如我們供應了一個Class
對象給其他人,或許說一個ES5
版本的組織函數。
假如沒有運用new
關鍵字來挪用的話,Class
對象會直接拋出非常,而ES5
中的組織函數this
指向則會變成挪用函數時的作用域。
我們可以運用apply
這個trap
來兼容這類狀況:
class Test {
constructor (a, b) {
console.log('constructor', a, b)
}
}
// Test(1, 2) // throw an error
let proxyClass = new Proxy(Test, {
apply (target, thisArg, argumentsList) {
// 假如想要制止運用非new的體式格局來挪用函數,直接拋出非常即可
// throw new Error(`Function ${target.name} cannot be invoked without 'new'`)
return new (target.bind(thisArg, ...argumentsList))()
}
})
proxyClass(1, 2) // constructor 1 2
我們運用了apply
來代辦一些行動,在函數挪用時會被觸發,由於我們明白的曉得,代辦的是一個Class
或組織函數,所以我們直接在apply
中運用new
關鍵字來挪用被代辦的函數。
以及假如我們想要對函數舉行限定,制止運用new
關鍵字來挪用,可以用另一個trap
:construct
function add (a, b) {
return a + b
}
let proxy = new Proxy(add, {
construct (target, argumentsList, newTarget) {
throw new Error(`Function ${target.name} cannot be invoked with 'new'`)
}
})
proxy(1, 2) // 3
new proxy(1, 2) // throw an error
用Proxy來包裝fetch
在前端發送要求,我們如今經常常運用到的應當就是fetch
了,一個原生供應的API。
我們可以用Proxy
來包裝它,使其變得更易用。
let handlers = {
get (target, property) {
if (!target.init) {
// 初始化對象
['GET', 'POST'].forEach(method => {
target[method] = (url, params = {}) => {
return fetch(url, {
headers: {
'content-type': 'application/json'
},
mode: 'cors',
credentials: 'same-origin',
method,
...params
}).then(response => response.json())
}
})
}
return target[property]
}
}
let API = new Proxy({}, handlers)
await API.GET('XXX')
await API.POST('XXX', {
body: JSON.stringify({name: 1})
})
對GET
、POST
舉行了一層封裝,可以直接經由歷程.GET
這類體式格局來挪用,並設置一些通用的參數。
完成一個淺易的斷言東西
寫過測試的列位童鞋,應當都邑曉得斷言這個東西 console.assert
就是一個斷言東西,接收兩個參數,假如第一個為false
,則會將第二個參數作為Error message
拋出。
我們可以運用Proxy
來做一個直接賦值就可以完成斷言的東西。
let assert = new Proxy({}, {
set (target, message, value) {
if (!value) console.error(message)
}
})
assert['Isn\'t true'] = false // Error: Isn't true
assert['Less than 18'] = 18 >= 19 // Error: Less than 18
統計函數挪用次數
在做服務端時,我們可以用Proxy
代辦一些函數,來統計一段時間內挪用的次數。
在後期做機能剖析時可以會可以用上:
function orginFunction () {}
let proxyFunction = new Proxy(orginFunction, {
apply (target, thisArg. argumentsList) {
log(XXX)
return target.apply(thisArg, argumentsList)
}
})
悉數的traps
這裏列出了handlers
一切可以定義的行動 (traps):
詳細的可以檢察
MDN-Proxy裡邊一樣有一些例子
traps | description |
---|---|
get | 獵取某個key 值 |
set | 設置某個key 值 |
has | 運用in 操縱符推斷某個key 是不是存在 |
apply | 函數挪用,僅在代辦對象為function 時有用 |
ownKeys | 獵取目的對象一切的key |
construct | 函數經由歷程實例化挪用,僅在代辦對象為function 時有用 |
isExtensible | 推斷對象是不是可擴大,Object.isExtensible 的代辦 |
deleteProperty | 刪除一個property |
defineProperty | 定義一個新的property |
getPrototypeOf | 獵取原型對象 |
setPrototypeOf | 設置原型對象 |
preventExtensions | 設置對象為不可擴大 |
getOwnPropertyDescriptor | 獵取一個自有屬性 (不會去原型鏈查找) 的屬性形貌 |