原文: Javascript Proxies: Real World Use Case -- Arbaz Siddiquijavascript
譯者注, 爲了防止出現"魯棒性"這種因翻譯習慣差別致使的混淆, 文中部分術語將不會進行翻譯.java
在編程術語範疇中, Proxy指的是幫助/替代另外一個實體(Entity)完成一系列操做的實體. 一個架設在客戶端與服務端之間的Proxy服務器分別充當了客戶端的服務端和服務端的客戶端. 對於Proxy來講, 它們的任務就是介入收到的請求/調用, 並在處理後傳遞給其上游. 這些介入容許Proxy添加一些額外的業務邏輯或者改變整個操做的行爲.git
JavaScript的Proxy從某種意義上來講是類似的. 它處在代碼所操做的對象與實際被操做的對象之間進行處理.github
根據MDN Web文檔shell
The Proxy is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).編程
Proxy被用來自定義一些基礎層面的操做(例如屬性查找, 賦值, 枚舉, 函數調用等)緩存
在完成一個Proxy的使用以前, 有三個術語須要咱們提早進行了解:服務器
Target就是實際被Proxy操做修改的對象. 它能夠是任何一個JavaScript對象.ide
譯者注: 這個地方的翻譯說實話有點不太好翻譯, 但實際上只要可以理解所謂Traps就是用來重載(代理)Target對應的名字的屬性/方法的屬性/方法就行函數
Traps是指那些在Target的屬性或者方法被調用時會介入干涉的方法. 有許多定義了的Traps能夠被實現(implement)
Handler是一個全部的Traps生存的佔位對象. 簡單來講, 能夠把它當作一個存放且實現各個traps的對象.
咱們來看看下面這個例子:
//movie is a target
const movie = {
name: "Pulp Fiction",
director: "Quentin Tarantino"
};
//this is a handler
const handler = {
//get is a trap
get: (target, prop) => {
if (prop === 'director') {
return 'God'
}
return target[prop]
},
set: function (target, prop, value) {
if (prop === 'actor') {
target[prop] = 'John Travolta'
} else {
target[prop] = value
}
}
};
const movieProxy = new Proxy(movie, handler);
console.log(movieProxy.director); //God
movieProxy.actor = "Tim Roth";
movieProxy.actress = "Uma Thurman";
console.log(movieProxy.actor); //John Travolta
console.log(movieProxy.actress); //Uma Thurman
複製代碼
輸出以下
God
John Travolta
Uma Thurman
複製代碼
上面這個例子中, movie
就是咱們所說的Target. 咱們實現了一個擁有set
和get
這兩個trap的handler
. 在其中咱們添加了兩個邏輯: 在訪問director
時, get
這個trap會直接返回God
而不是它實際的值; 在對actor
賦值時, set
這個trap會干涉全部的賦值操做, 並在鍵爲actor
時將值改變成John Travlota
.
雖然並不如其餘的ES2015的特性那樣廣爲人知, Proxy仍是有諸如全部屬性的默認值這樣的如今看來挺亮眼的用例. 讓咱們來看看其餘的在真實生產環場景中可以利用Proxy的地方.
既然咱們已經能夠干涉對象的屬性賦值過程, 那麼咱們能夠藉此來校驗咱們將要賦予給對象屬性的值. 看下面這個例子
const handler = {
set: function (target, prop, value) {
const houses = ['Stark', 'Lannister'];
if (prop === 'house' && !(houses.includes(value))) {
throw new Error(`House ${value} does not belong to allowed ${houses}`)
}
target[prop] = value
}
};
const gotCharacter = new Proxy({}, handler);
gotCharacter.name = "Jamie";
gotCharacter.house = "Lannister";
console.log(gotCharacter);
gotCharacter.name = "Oberyn";
gotCharacter.house = "Martell";
複製代碼
運行結果以下:
{ name: 'Jamie', house: 'Lannister' }
Error: House Martell does not belong to allowed Stark,Lannister
複製代碼
上面這個例子中, 咱們嚴格限制了house
這個屬性所能被賦予的值的範圍. 只須要建立一個set
的trap, 咱們甚至能用這個實現方式來實現一個只讀的對象.
咱們能夠經過Proxy來建立一個在讀寫屬性時的反作用. 出發點在於某些特定的屬性被訪問或者寫入時觸發一些函數. 看下面這個例子:
const sendEmail = () => {
console.log("sending email after task completion")
};
const handler = {
set: function (target, prop, value) {
if (prop === 'status' && value === 'complete') {
sendEmail()
}
target[prop] = value
}
};
const tasks = new Proxy({}, handler);
tasks.status = "complete";
複製代碼
運行結果以下:
sending email after task completion
複製代碼
這裏咱們干涉了status
這個屬性的寫入. 當寫入的值是complete
時, 會觸發一個反作用函數. 在Sindre Sorhus的on-change這個包中就一個很Cooooooool的實現.
利用介入干涉對象屬性讀寫的能力, 咱們可以建立一個基於內存的緩存. 它只會在值過時前返回值. 看下面這個例子:
const cacheTarget = (target, ttl = 60) => {
const CREATED_AT = Date.now();
const isExpired = () => (Date.now() - CREATED_AT) > (ttl * 1000);
const handler = {
get: (target, prop) => isExpired() ? undefined : target[prop]
};
return new Proxy(target, handler)
};
const cache = cacheTarget({age: 25}, 5);
console.log(cache.age);
setTimeout(() => {
console.log(cache.age)
}, 6 * 1000);
複製代碼
運行結果以下:
25
undefined
複製代碼
這裏咱們建立了一個函數, 並返回一個Proxy. 在獲取target的屬性前, 這個Proxy的handler首先會檢查target對象是否過時. 基於此, 咱們能夠針對每一個鍵值都設置一個基於TTLs或者其餘機制的過時檢查.
雖然Proxy具有一些很神奇的功能, 但在使用時仍然具備一些不得不當心應對的限制:
Proxy很強, 在很大範圍內都可以獲得應用, 或者被濫用. 這篇文章中咱們討論了什麼是Proxy, 如何實現一個Proxy, 幾個真實案例中的用例, 以及它的缺陷限制.