[譯] JavaScript Proxy -- 一些真實的用例

原文: Javascript Proxies: Real World Use Case -- Arbaz Siddiquijavascript

譯者注, 爲了防止出現"魯棒性"這種因翻譯習慣差別致使的混淆, 文中部分術語將不會進行翻譯.java

Proxy介紹

在編程術語範疇中, 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(目標)

Target就是實際被Proxy操做修改的對象. 它能夠是任何一個JavaScript對象.ide

Traps(阱)

譯者注: 這個地方的翻譯說實話有點不太好翻譯, 但實際上只要可以理解所謂Traps就是用來重載(代理)Target對應的名字的屬性/方法的屬性/方法就行函數

Traps是指那些在Target的屬性或者方法被調用時會介入干涉的方法. 有許多定義了的Traps能夠被實現(implement)

Handler(處理器)

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. 咱們實現了一個擁有setget這兩個trap的handler. 在其中咱們添加了兩個邏輯: 在訪問director時, get這個trap會直接返回God而不是它實際的值; 在對actor賦值時, set這個trap會干涉全部的賦值操做, 並在鍵爲actor時將值改變成John Travlota.

真實的案例

雖然並不如其餘的ES2015的特性那樣廣爲人知, Proxy仍是有諸如全部屬性的默認值這樣的如今看來挺亮眼的用例. 讓咱們來看看其餘的在真實生產環場景中可以利用Proxy的地方.

驗證 Validation

既然咱們已經能夠干涉對象的屬性賦值過程, 那麼咱們能夠藉此來校驗咱們將要賦予給對象屬性的值. 看下面這個例子

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, 咱們甚至能用這個實現方式來實現一個只讀的對象.

反作用 Side Effects

咱們能夠經過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 Sorhuson-change這個包中就一個很Cooooooool的實現.

緩存 Caching

利用介入干涉對象屬性讀寫的能力, 咱們可以建立一個基於內存的緩存. 它只會在值過時前返回值. 看下面這個例子:

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具有一些很神奇的功能, 但在使用時仍然具備一些不得不當心應對的限制:

  1. 性能會受到顯著的影響. 在注重性能的代碼中應該避免對Proxy的使用
  2. 沒有辦法區分判斷一個對象是一個Proxy的對象或者是target的對象
  3. Proxy可能會致使代碼在可讀性上面出現問題

總結

Proxy很強, 在很大範圍內都可以獲得應用, 或者被濫用. 這篇文章中咱們討論了什麼是Proxy, 如何實現一個Proxy, 幾個真實案例中的用例, 以及它的缺陷限制.

相關文章
相關標籤/搜索