摘要:虛擬代理延遲執(zhí)行虛擬代理的目的,是將開銷大的運算延遲到需要時再執(zhí)行。
代理模式:為一個對象提供一個代用品或占位符,以便控制它的訪問。
當我們不方便直接訪問某個對象時,或不滿足需求時,可考慮使用一個替身對象來控制該對象的訪問。替身對象可對請求預先進行處理,再決定是否轉交給本體對象。
生活小栗子:
代購;
明星經(jīng)紀人;
和諧上網(wǎng)
經(jīng)常 shopping 的同學,對代購應該不陌生。自己不方便直接購買或買不到某件商品時,會選擇委托給第三方,讓代購或黃牛去做購買動作。程序世界的代理者也是如此,我們不直接操作原有對象,而是委托代理者去進行。代理者的作用,就是對我們的請求預先進行處理或轉接給實際對象。
模式特點代理對象可預先處理請求,再決定是否轉交給本體;
代理和本體對外顯示接口保持一致性
代理對象僅對本體做一次包裝
模式細分虛擬代理(將開銷大的運算延遲到需要時執(zhí)行)
緩存代理(為開銷大的運算結果提供緩存)
保護代理(黑白雙簧,代理充當黑臉,攔截非分要求)
防火墻代理(控制網(wǎng)絡資源的訪問)
遠程代理(為一個對象在不同的地址控件提供局部代表)
智能引用代理(訪問對象執(zhí)行一些附加操作)
寫時復制代理(延遲對象復制過程,對象需要真正修改時才進行)
JavaScript 中常用的代理模式為 “虛擬代理” 和 “緩存代理”。
模式實現(xiàn)實現(xiàn)方式:創(chuàng)建一個代理對象,代理對象可預先對請求進行處理,再決定是否轉交給本體,代理和本體對外接口保持一致性(接口名相同)。
// 例子:代理接聽電話,實現(xiàn)攔截黑名單 var backPhoneList = ["189XXXXX140"]; // 黑名單列表 // 代理 var ProxyAcceptPhone = function(phone) { // 預處理 console.log("電話正在接入..."); if (backPhoneList.includes(phone)) { // 屏蔽 console.log("屏蔽黑名單電話"); } else { // 轉接 AcceptPhone.call(this, phone); } } // 本體 var AcceptPhone = function(phone) { console.log("接聽電話:", phone); }; // 外部調用代理 ProxyAcceptPhone("189XXXXX140"); ProxyAcceptPhone("189XXXXX141");
代理并不會改變本體對象,遵循 “單一職責原則”,即 “自掃門前雪,各找各家”。不同對象承擔獨立職責,不過于緊密耦合,具體執(zhí)行功能還是本體對象,只是引入代理可以選擇性地預先處理請求。例如上述代碼中,我們向 “接聽電話功能” 本體添加了一個屏蔽黑名單的功能(保護代理),預先處理電話接入請求。
虛擬代理(延遲執(zhí)行)虛擬代理的目的,是將開銷大的運算延遲到需要時再執(zhí)行。
虛擬代理在圖片預加載的應用,代碼例子來至 《JavaScript 設計模式與開發(fā)實踐》
// 本體 var myImage = (function(){ var imgNode = document.createElement("img"); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } } })(); // 代理 var proxyImage = (function(){ var img = new Image; img.onload = function() { myImage.setSrc(this.src); // 圖片加載完設置真實圖片src } return { setSrc: function(src) { myImage.setSrc("./loading.gif"); // 預先設置圖片src為loading圖 img.src = src; } } })(); // 外部調用 proxyImage.setSrc("./product.png"); // 有l(wèi)oading圖的圖片預加載效果緩存代理(暫時存儲)
緩存代理的目的,是為一些開銷大的運算結果提供暫時存儲,以便下次調用時,參數(shù)與結果不變情況下,從緩存返回結果,而不是重新進行本體運算,減少本體調用次數(shù)。
應用緩存代理的本體,要求運算函數(shù)應是一個純函數(shù),簡單理解比如一個求和函數(shù) sum, 輸入?yún)?shù) (1, 1), 得到的結果應該永遠是 2。
純函數(shù):固定的輸入,有固定的輸出,不影響外部數(shù)據(jù)。
模擬場景:60道判斷題測試,每三道題計分一次,根據(jù)計分篩選下一步的三道題目?
三道判斷題得分結果:
(0, 0 ,0)
(0, 0, 1)
(0, 1, 0)
(0, 1, 1)
(1, 0, 0)
(1, 0, 1)
(1, 1, 0)
(1, 1, 1)
總共七種計分結果。60/3 = 20,共進行 20 次計分,每次計分執(zhí)行 3 個循環(huán)累計,共 60 個循環(huán)。接下來,借用 “緩存代理” 方式,來實現(xiàn)最少本體運算次數(shù)。
// 本體:對三道題答案進行計分 var countScore = function(ansList) { let [a, b, c] = ansList; return a + b + c; } // 代理:對計分請求預先處理 var proxyCountScore = (function() { var existScore = {}; // 設定存儲對象 return function(ansList) { var attr = ansList.join(","); // eg. ["0,0,0"] if (existScore[attr] != null) { // 從內(nèi)存返回 return existScore[attr]; } else { // 內(nèi)存不存在,轉交本體計算并存入內(nèi)存 return existScore[attr] = countScore(ansList); } } })(); // 調用計分 proxyCountScore([0,1,0]);
60 道題目,每 3 道題一次計分,共 20 次計分運算,但總的計分結果只有 7 種,那么實際上本體 countScore() 最多只需運算 7 次,即可囊括所有計算結果。
通過緩存代理的方式,對計分結果進行臨時存儲。用答案字符串組成屬性名 ["0,1,0"] 作為 key 值檢索內(nèi)存,若存在直接從內(nèi)存返回,減少包含復雜運算的本體被調用的次數(shù)。之后如果我們的題目增加至 90 道, 120 道,150 道題時,本體 countScore() 運算次數(shù)仍舊保持 7 次,中間節(jié)省了復雜運算的開銷。
ES6 的 ProxyES6新增的 Proxy 代理對象的操作,具體的實現(xiàn)方式是在 handler 上定義對象自定義方法集合,以便預先管控對象的操作。
ES6 的 Proxy語法:let proxyObj = new Proxy(target, handler);
target: 本體,要代理的對象
handler: 自定義操作方法集合
proxyObj: 返回的代理對象,擁有本體的方法,不過會被 handler 預處理
// ES6的Proxy let Person = { name: "以樂之名" }; const ProxyPerson = new Proxy(Person, { get(target, key, value) { if (key != "age") { return target[key]; } else { return "保密" } }, set(target, key, value) { if (key === "rate") { target[key] = value === "A" ? "推薦" : "待提高" } } }) console.log(ProxyPerson.name); // "以樂之名" console.log(ProxyPerson.age); // "保密" ProxyPerson.rate = "A"; console.log(ProxyPerson.rate); // "推薦" ProxyPerson.rate = "B"; console.log(ProxyPerson.rate); // "待提高"
handler 除常用的 set/get,總共支持 13 種方法:
handler.getPrototypeOf() // 在讀取代理對象的原型時觸發(fā)該操作,比如在執(zhí)行 Object.getPrototypeOf(proxy) 時 handler.setPrototypeOf() // 在設置代理對象的原型時觸發(fā)該操作,比如在執(zhí)行 Object.setPrototypeOf(proxy, null) 時 handler.isExtensible() // 在判斷一個代理對象是否是可擴展時觸發(fā)該操作,比如在執(zhí)行 Object.isExtensible(proxy) 時 handler.preventExtensions() // 在讓一個代理對象不可擴展時觸發(fā)該操作,比如在執(zhí)行 Object.preventExtensions(proxy) 時 handler.getOwnPropertyDescriptor() // 在獲取代理對象某個屬性的屬性描述時觸發(fā)該操作,比如在執(zhí)行 Object.getOwnPropertyDescriptor(proxy, "foo") 時 handler.defineProperty() // 在定義代理對象某個屬性時的屬性描述時觸發(fā)該操作,比如在執(zhí)行 Object.defineProperty(proxy, "foo", {}) 時 handler.has() // 在判斷代理對象是否擁有某個屬性時觸發(fā)該操作,比如在執(zhí)行 "foo" in proxy 時 handler.get() // 在讀取代理對象的某個屬性時觸發(fā)該操作,比如在執(zhí)行 proxy.foo 時 handler.set() // 在給代理對象的某個屬性賦值時觸發(fā)該操作,比如在執(zhí)行 proxy.foo = 1 時 handler.deleteProperty() // 在刪除代理對象的某個屬性時觸發(fā)該操作,比如在執(zhí)行 delete proxy.foo 時 handler.ownKeys() // 在獲取代理對象的所有屬性鍵時觸發(fā)該操作,比如在執(zhí)行 Object.getOwnPropertyNames(proxy) 時 handler.apply() // 在調用一個目標對象為函數(shù)的代理對象時觸發(fā)該操作,比如在執(zhí)行 proxy() 時。 handler.construct() // 在給一個目標對象為構造函數(shù)的代理對象構造實例時觸發(fā)該操作,比如在執(zhí)行 new proxy() 時適用場景
虛擬代理:
圖片預加載(loading 圖)
合并HTTP請求(數(shù)據(jù)上報匯總)
緩存代理:(前提本體是純函數(shù))
緩存異步請求數(shù)據(jù)
緩存較復雜的運算結果
ES6 的 Proxy:
實現(xiàn)對象私有屬性
實現(xiàn)表單驗證
“策略模式” 可應用于表單驗證信息,“代理方式” 也可實現(xiàn)。這里引用 Github - jawil 的一個例子,思路供大家分享。
// 利用 proxy 攔截格式不符數(shù)據(jù) function validator(target, validator, errorMsg) { return new Proxy(target, { _validator: validator, set(target, key, value, proxy) { let errMsg = errorMsg; if (value == null || !value.length) { console.log(`${errMsg[key]} 不能為空`); return target[key] = false; } let va = this._validator[key]; // 這里有策略模式的應用 if (!!va(value)) { return Reflect.set(target, key, value, proxy); } else { console.log(`${errMsg[key]} 格式不正確`); return target[key] = false; } } }) } // 負責校驗的邏輯代碼 const validators = { name(value) { return value.length >= 6; }, passwd(value) { return value.length >= 6; }, moblie(value) { return /^1(3|5|7|8|9)[0-9]{9}$/.test(value); }, email(value) { return /^w+([+-.]w+)*@w+([-.]w+)*.w+([-.]w+)*$/.test(value) } } // 調用代碼 const errorMsg = { name: "用戶名", passwd: "密碼", moblie: "手機號碼", email: "郵箱地址" } const vali = validator({}, validators, errorMsg) let registerForm = document.querySelector("#registerForm") registerForm.addEventListener("submit", function () { let validatorNext = function* () { yield vali.name = registerForm.userName.value yield vali.passwd = registerForm.passWord.value yield vali.moblie = registerForm.phone.value yield vali.email = registerForm.email.value } let validator = validatorNext(); for (let field of validator) { validator.next(); } }
實現(xiàn)思路: 利用 ES6 的 proxy 自定義 handler 的 set() ,進行表單校驗并返回結果,并且借用 “策略模式" 獨立封裝驗證邏輯。使得表單對象,驗證邏輯,驗證器各自獨立。代碼整潔性,維護性及復用性都得到增強。
關于 “設計模式” 在表單驗證的應用,可參考 jawil 原文:《探索兩種優(yōu)雅的表單驗證——策略設計模式和ES6的Proxy代理模式》。
優(yōu)缺點
優(yōu)點:
可攔截和監(jiān)聽外部對本體對象的訪問;
復雜運算前可以進行校驗或資源管理;
對象職能粒度細分,函數(shù)功能復雜度降低,符合 “單一職責原則”;
依托代理,可額外添加擴展功能,而不修改本體對象,符合 “開發(fā)-封閉原則”
缺點:
額外代理對象的創(chuàng)建,增加部分內(nèi)存開銷;
處理請求速度可能有差別,非直接訪問存在開銷,但 “虛擬代理” 及 “緩存代理” 均能提升性能
參考文章
《JavaScript 設計模式與開發(fā)實踐》
《ES6中的代理模式-----Proxy》
《探索兩種優(yōu)雅的表單驗證——策略設計模式和ES6的Proxy代理模式》
Github,期待Star!
https://github.com/ZengLingYong/blog
作者:以樂之名
本文原創(chuàng),有不當?shù)牡胤綒g迎指出。轉載請指明出處。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104978.html
摘要:單例模式的定義是保證一個類只有僅有一個實例,并提供一個訪問它的全局訪問點。并且按照單一職責原則類實現(xiàn)功能類管理單例管理單例模式,達到可組合的的效果創(chuàng)建普通類引入代理類惰性單例模式分離創(chuàng)建實例對象的職責與管理單例的職責。 單例模式的定義是:保證一個類只有僅有一個實例,并提供一個訪問它的全局訪問點。 單例模式是一種常用的模式,有些對象我們往往只需要一個,比如線程池,全局緩存,window對...
摘要:說白了,就是對訪問進行控制。這一回,主要聊了代理模式,虛擬代理,圖片懶加載,難度適中下一回,聊一下適配器模式,難度也比較小。 本回內(nèi)容介紹 上一回,聊了門面模式,DOM2級事件,事件委托,介一回,也比較容易,代理模式(proxy),代理對象控制對本體對象的訪問,實現(xiàn)了同樣的接口,并且會把任何方法的調用傳遞到本體對象。說白了,就是對訪問進行控制。直接上代碼,走你: 1.代理模式 代理也是...
摘要:代理模式代理模式為一個對象提供一個代用品或占位符,以便控制對于它訪問。這種代理就叫虛擬代理。保護代理用于對象應該有不同訪問權限情況。寫時復制代理時虛擬代理的一種變體。 一、創(chuàng)建型設計模式(三大類設計模式) 創(chuàng)建型設計模式 --創(chuàng)建說明該類別里面的設計模式就是用來創(chuàng)建對象的,也就是在不同的場景下我們應該選用什么樣的方式來創(chuàng)建對象。 1. 單例模式 ==單例模式(Singleton)==:...
摘要:針對上面看到的問題,現(xiàn)在也有一些團隊在嘗試前后端之間加一個中間層比如淘寶的。淘寶有很多類似的文章,這里不贅述。淘寶團隊做了兩套接口文檔的維護工具,以及,不知道有沒有對外開放,兩個東西都是基于的一個嘗試,各有優(yōu)劣。 原文出處: 小胡子哥的博客(@Barret李靖) 前后端分工協(xié)作是一個老生常談的大話題,很多公司都在嘗試用工程化的方式去提升前后端之間交流的效率,降低溝通成本,并且也開發(fā)了...
摘要:針對上面看到的問題,現(xiàn)在也有一些團隊在嘗試前后端之間加一個中間層比如淘寶的。淘寶有很多類似的文章,這里不贅述。淘寶團隊做了兩套接口文檔的維護工具,以及,不知道有沒有對外開放,兩個東西都是基于的一個嘗試,各有優(yōu)劣。 原文出處: 小胡子哥的博客(@Barret李靖) 前后端分工協(xié)作是一個老生常談的大話題,很多公司都在嘗試用工程化的方式去提升前后端之間交流的效率,降低溝通成本,并且也開發(fā)了...
閱讀 1876·2021-09-24 09:48
閱讀 3220·2021-08-26 14:14
閱讀 1674·2021-08-20 09:36
閱讀 1462·2019-08-30 15:55
閱讀 3628·2019-08-26 17:15
閱讀 1426·2019-08-26 12:09
閱讀 607·2019-08-26 11:59
閱讀 3324·2019-08-26 11:57