一個對象提供一個代用品或佔位符,以便控制對它的訪問。代理控制着對於原對象的訪問, 並容許在將請求提交給對象先後進行一些處理。html
信用卡是銀行帳戶的代理, 銀行帳戶則是一大捆現金的代理。 它們都實現了一樣的接口, 都可用於進行支付。 消費者會很是滿意, 由於沒必要隨身攜帶大量現金; 商店老闆一樣會十分高興, 由於交易收入能以電子化的方式進入商店的銀行帳戶中, 無需擔憂存款時出現現金丟失或被搶劫的狀況。web
功能需求:用一張loading
圖佔位,而後用異步的方式加載圖片,等圖片加載好了再把它填充到 img 節點裏。緩存
首先建立一個本體對象,負責往頁面中建立 img 標籤,並提供一個setSrc
接口,用於外界設置 img 的src
屬性:服務器
const myImage = (function() { const imgNode = document.createElement("img"); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } }; })(); myImage.setSrc("http://qiniu.johnsenzhou.com/FmC3WzznEuwQwhEKn9YWn43phArJ"); 複製代碼
如今開始引入代理對象proxyImage
,在圖片沒加載出來前用loading
圖佔位,提示用戶圖片正在加載。markdown
const proxyImage = (function() { const img = new Image(); img.onload = function() { myImage.setSrc(this.src); }; return { setSrc: function(src) { myImage.setSrc( "http://www.sucaijishi.com/uploadfile/2015/0210/20150210104951657.gif" ); img.src = src; } }; })(); proxyImage.setSrc("http://qiniu.johnsenzhou.com/FmC3WzznEuwQwhEKn9YWn43phArJ"); 複製代碼
如今咱們經過proxyImage
間接訪問myImage
。proxyImage
控制了myImage
對圖片的直接操做,在此過程當中加入一系列操做,而後再將處理好的請求轉交給myImage
。網絡
首先咱們引入一個面向對象設計的原則——單一職責原則。app
就一個類而言,應該只有一個引發它變化的緣由。若是一個對象承擔了多項職責,這個對象就會趨向臃腫,引發它變化的緣由可能會有多個,也就等於把這些職責耦合在一塊兒,這種耦合會致使脆弱和低內聚的設計。異步
而後再看以前寫的程序,咱們並無改變或者增長myImage
的接口,可是經過代理對象給它添加了新的行爲。這符合開放-封閉原則。給img
節點設置src
和圖片預加載這兩個功能隔離在兩個對象中,他們能夠各自變化而不影響對方。若是後期不須要預加載了,只須要請求本體而不是請求代理對象便可。優化
上面說到若是後期不須要預加載功能是,只用改爲直接請求本體便可。其中關鍵是代理和本體都具備setSrcd
的接口。在客戶看來,代理對象和本體對象是一致的。這樣作有兩個好處:this
在 web 開發中,也許最大的開銷就是網絡請求。假設咱們在作一個文件同步功能,當咱們每選擇一個CheckBox
,他對應的文件就同步到另外一臺服務器上,以下圖所示:
首先咱們先放置好checkbox
節點:
<div> <input type="checkbox" id="1" /> <input type="checkbox" id="2" /> <input type="checkbox" id="3" /> <input type="checkbox" id="4" /> <input type="checkbox" id="5" /> <input type="checkbox" id="6" /> <input type="checkbox" id="7" /> </div> 複製代碼
而後肯定代理對象處理邏輯:
收集 2 秒內的用戶請求,等待 2 秒之後再把這 2 秒內須要同步的文件打包發給服務器。
const uploadFile = id => { console.log("開始上傳文件,id爲", id); }; const proxyPploadFile = (function() { const cache = []; let timer; return function(id) { cache.push(id); if (timer) return; // 保證不覆蓋已啓動的定時器 timer = setTimeout(() => { uploadFile(cache.join(",")); clearTimeout(timer); timer = null; cache.length = 0; }, 2000); }; })(); const checkboxs = document.getElementsByTagName("input"); checkboxs.forEach(item => { item.onclick = function() { if (this.checked === true) { proxyPploadFile(this.id); } }; }); 複製代碼
緩存代理可暫存一些開銷大的運算結果,以便於下次運行一樣的參數時直接返回結果。
const mult = function() { let result = 1; for (let i = 0; i < arguments.length; i++) { result = result * arguments[i]; } return result; }; const proxyMult = (function() { const cache = {}; return function() { const args = Array.prototype.join.call(arguments, ","); if (args in cache) { return cache[args]; } return (cache[args] = mult.apply(this, arguments)); }; })(); proxyMult(1, 2, 3, 4); // 24 proxyMult(1, 2, 3, 4); // 24 複製代碼
緩存代理在優化列表翻頁的時候常常能夠用到:將以前選中的頁碼的數據緩存起來,後續再篩選到這個頁碼時直接從緩存中讀取。
代理模式很是有用,可是咱們在編寫業務代碼時每每不須要去預測是否須要使用搭理模式,當真正發現不方便的時候再引入代理模式也不遲。