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