看到 Proxy就應該想到代理模式(Proxy Pattern),php
Proxy
是 Javascript ES2015 標準的一部分,咱們應該學會使用它,代理模式是一種設計模式,使用算法
Proxy
對象能夠垂手可得的在 Javascript 中建立代理模式。然而,使用設計模式並非目的,目的在於解決實際問題。本文首先會簡單介紹設計模式
Proxy
的基本用法,接着將會敘述如何使用數組
Proxy
建立代理模式而且對咱們的應用進行優化。
Proxy 的基本使用 開始學習promise
Proxy
的使用以前,建議首先對 Reflect 有必定的瞭解,若是很陌生的話,建議先花 1 分鐘瀏覽相關知識。
好了,如今假設已經具有了必定的緩存
Reflect
知識,就開始掌握網絡
Proxy
吧。
基本語法 和app
Proxy
相關的方法一共就兩個:
構造方法 本文着重討論異步
Proxy.revocable()
建立一個可撤銷的async
Proxy
對象,其他與構造函數相似,理解了
Proxy
的構造方法後,該方法與構造方法使用很是相似,本文再也不涉及
接下來本文將圍繞
Proxy
的構造方法進行講解。
let p = new Proxy(target, handler);
參數
target 任何類型的對象,包括原生數組,函數,甚至另外一個
Proxy
對象
handler 一個對象,其屬性是當執行一個操做時定義代理的行爲的函數, 容許的屬性一共 13 種,與
Reflect
的方法名一致
返回
p
Proxy
對象
注意:
new Proxy
是穩定操做,不會對
target
有任何影響。
下面來看幾個表明性的例子,便於加深理解。
代理一個對象字面量:
const target = {}; const handler = { set: (obj, prop, value) => { obj[prop] = 2 * value; }, get: (obj, prop) => { return obj[prop] * 2; } }; const p = new Proxy(target, handler); p.x = 1; // 使用了 set 方法 console.log(p.x); // 4, 使用了 get 方法
代理一個數組:
const p = new Proxy( ['Adela', 'Melyna', 'Lesley'], { get: (obj, prop) => { if (prop === 'length') return `Length is ${obj[prop]}.`; return `Hello, ${obj[prop]}!`; } } ); console.log(p.length) // Length is 3. console.log(p[0]); // Hello, Adela console.log(p[1]); // Hello, Melyna console.log(p[2]); // Hello, Lesley
代理一個普通函數:
const foo = (a, b, c) => { return a + b + c; } const pFoo = new Proxy(foo, { apply: (target, that, args) => { const grow = args.map(x => x * 2); const inter = Reflect.apply(target, that, grow); return inter * 3; } }); pFoo(1, 2, 3); // 36, (1 * 2 + 2 * 2 + 3 * 2) * 3
代理構造函數
class Bar { constructor(x) { this.x = x; } say() { console.log(`Hello, x = ${this.x}`); } } const PBar = new Proxy(Bar, { construct: (target, args) => { const obj = new Bar(args[0] * 2); return obj; } }); const p = new PBar(1); p.say(); // Hello, x = 2
Proxy
的基本用法無出其上,可
Proxy
的真正用途尚未顯現出來,接下來結合設計模式中的一種模式 —— 代理模式 —— 進一步討論。
使用 Proxy 建立代理模式 從上面的例子並不能看出
Proxy
給咱們帶來了什麼便利,須要實現的功能徹底能夠在原函數內部進行實現。既然如此,使用代理模式的意義是什麼呢?
遵循「單一職責原則」,面向對象設計中鼓勵將不一樣的職責分佈到細粒度的對象中,
Proxy
在原對象的基礎上進行了功能的衍生而又不影響原對象,符合鬆耦合高內聚的設計理念
遵循「開放-封閉原則」,代理能夠隨時從程序中去掉,而不用對其餘部分的代碼進行修改,在實際場景中,隨着版本的迭代可能會有多種緣由再也不須要代理,那麼就能夠容易的將代理對象換成原對象的調用
達到上述兩個原則有一個前提就是代理必須符合「代理和本體接口一致性」原則:代理和原對象的輸入和輸出必須是一致的。這樣對於用戶來講,代理就是透明的,代理和原對象在不改動其餘代碼的條件下是能夠被相互替換的。
代理模式的用途很普遍,這裏咱們看一個緩存代理的例子。
首先建立一個
Proxy
的包裝函數,該函數接受須要建立代理的目標函數爲第一個參數,以緩存的初值爲第二個參數:
const createCacheProxy = (fn, cache = new Map()) => { return new Proxy(fn, { apply(target, context, args) { const argsProp = args.join(' '); if (cache.has(argsProp)) { console.log('Using old data...'); return cache.get(argsProp); } const result = fn(...args); cache.set(argsProp, result); return result; } }); };
而後咱們使用乘法函數
mult
去建立代理並調用:
const mult = (...args) => args.reduce((a, b) => a * b); const multProxy = createCacheProxy(mult); multProxy(2, 3, 4); // 24 multProxy(2, 3, 4); // 24, 輸出 Using old data
也可使用其餘的函數:
const squareAddtion = (...args) => args.reduce((a, b) => a + b ** 2, 0); const squareAddtionProxy = createCacheProxy(squareAddtion); squareAddtionProxy(2, 3, 4); // 29 squareAddtionProxy(2, 3, 4); // 29, 輸出 Using old data
對於上面這個例子,有三點須要注意:
對於檢測是否存在舊值的過程較爲粗暴,實際應用中應考慮是否應該使用更爲複雜精確的判斷方法,須要結合實際進行權衡;
createCacheProxy
中的
console.log
違背了前文所說的「代理和本體接口一致性」原則,只是爲了開發環境更加方便性的調試,生產環境中必須去掉;
multProxy
與
squareAdditionProxy
是爲了演示使用方法而在這裏使用了相對簡單的算法和小數據量,但在實際應用中數據量越大、
fn
的計算過程越複雜,優化效果越好,不然,優化效果不只有可能不明顯反而會形成性能降低
代理模式的實際應用 這一節結合幾個具體的例子來加深對代理模式的理解。
函數節流 若是想要控制函數調用的頻率,可使用代理進行控制:
須要實現的基本功能:
const handler = () => console.log('Do something...'); document.addEventListener('click', handler);
接下來使用
Proxy
進行節流。
首先使用構造建立代理函數:
const createThrottleProxy = (fn, rate) => { let lastClick = Date.now() - rate; return new Proxy(fn, { apply(target, context, args) { if (Date.now() - lastClick >= rate) { fn(args); lastClick = Date.now(); } } }); };
而後只須要將原有的事件處理函數進行一曾包裝便可:
const handler = () => console.log('Do something...'); const handlerProxy = createThrottleProxy(handler, 1000); document.addEventListener('click', handlerProxy);
在生產環境中已有多種工具庫實現該功能,不須要咱們本身編寫
圖片懶加載 某些時候須要延遲加載圖片,尤爲要考慮網絡環境惡劣以及比較重視流量的狀況。這個時候可使用一個虛擬代理進行延遲加載。
首先是咱們最原始的代碼:
const img = new Image(); img.src = '/some/big/size/image.jpg'; document.body.appendChild(img);
爲了實現懶加載,建立虛擬圖片節點
virtualImg
並構造建立代理函數:
const createImgProxy = (img, loadingImg, realImg) => { let hasLoaded = false; const virtualImg = new Image(); virtualImg.src = realImg; virtualImg.onload = () => { Reflect.set(img, 'src', realImg); hasLoaded = true; } return new Proxy(img, { get(obj, prop) { if (prop === 'src' && !hasLoaded) { return loadingImg; } return obj[prop]; } }); };
最後是將原始的圖片節點替換爲代理圖片進行調用:
const img = new Image(); const imgProxy = createImgProxy(img, '/loading.gif', '/some/big/size/img.jpg'); document.body.appendChild(imgProxy);
異步隊列 這個需求是很常見的:前一個異步操做結束後再進行下一個異步操做。這部分我使用
Promise
進行實現。
首先構造一個最爲簡單的異步操做
asyncFunc
:
const callback = () => console.log('Do something...'); const asyncFunc = (cb) => { setTimeout(cb, 1000); } asyncFunc(callback); asyncFunc(callback); asyncFunc(callback);
能夠看到控制檯的輸出是 1s 以後,幾乎是同時輸出三個結果:
// .. 1s later .. Do something... Do something... Do something...
接下來咱們使用
Promise
實現異步隊列:
const createAsyncQueueProxy = (asyncFunc) => { let promise = null; return new Proxy(asyncFunc, { apply(target, context, [cb, ...args]) { promise = Promise .resolve(promise) .then(() => new Promise(resolve => { Reflect.apply(asyncFunc, this, [() => { cb(); resolve(); }, ...args]); })); } }); };
上面這段代碼經過
Promise
實現了異步函數隊列,建議在理解了
Promise
以後再理解閱讀上面這段代碼。
上面這段代碼測試經過,有兩點須要注意:
promise
的值並不能肯定是否爲
Promise
,須要使用
Promise.resolve
方法以後才能使用
then
方法
Reflect.apply
方法中的第三個參數是數組,形同與
Function.prototype.apply
的第二個參數
而後使用代理進行替換並調用:
const timeoutProxy = createAsyncQueueProxy(asynFunc); timeoutProxy(callback); timeoutProxy(callback); timeoutProxy(callback);
能夠看到控制檯的輸出已經像咱們指望的那樣: 前一個異步操做執行完畢以後纔會進行下一個異步操做。
// .. 1s later .. Do something... // .. 1s later .. Do something... // .. 1s later .. Do something...
除了上面這種使用代理的方式實現異步隊列外,在個人另外一篇博客進階 Javascript 生成器中,還使用了另一種方式。
結語 本文首先介紹了 ES2015 中關於
Proxy
的基本用法,接着討論了代理模式的使用特色,而後結合實際列舉了幾種常見的使用場景。最後列舉一些比較有價值的參考資料供感興趣的開發者繼續閱讀。
參考資料 Javascript Proxy Design Pattern
JavaScript設計模式與開發實踐 - 曾探
JavaScript Design Patterns: Proxy
轉載於猿2048:→《使用 Javascript 原生的 Proxy 優化應用》