摘要:此時,鏈家起到的作用就是代理的作用。驗證代理構(gòu)造函數(shù)第二個參數(shù)中的方法,可以很方便的驗證向一個對象的傳值。
1 什么是代理模式
為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標(biāo)對象之間起到中介的作用。
在生活中,代理模式的場景是十分常見的,例如我們現(xiàn)在如果有租房、買房的需求,更多的是去找鏈家等房屋中介機構(gòu),而不是直接尋找想賣房或出租房的人談。此時,鏈家起到的作用就是代理的作用。鏈家和他所代理的客戶在租房、售房上提供的方法可能都是一致的(收錢,簽合同),可是鏈家作為代理卻提供了訪問限制,讓我們不能直接訪問被代理的客戶。
在面向?qū)ο蟮木幊讨校砟J降暮侠硎褂媚軌蚝芎玫捏w現(xiàn)下面兩條原則:
單一職責(zé)原則: 面向?qū)ο笤O(shè)計中鼓勵將不同的職責(zé)分布到細(xì)粒度的對象中,Proxy 在原對象的基礎(chǔ)上進行了功能的衍生而又不影響原對象,符合松耦合高內(nèi)聚的設(shè)計理念。
開放-封閉原則:代理可以隨時從程序中去掉,而不用對其他部分的代碼進行修改,在實際場景中,隨著版本的迭代可能會有多種原因不再需要代理,那么就可以容易的將代理對象換成原對象的調(diào)用
2 ES6中的代理模式ES6所提供Proxy構(gòu)造函數(shù)能夠讓我們輕松的使用代理模式:
var proxy = new Proxy(target, handler);
Proxy構(gòu)造函數(shù)傳入兩個參數(shù),第一個參數(shù)target表示所要代理的對象,第二個參數(shù)handler也是一個對象用來設(shè)置對所代理的對象的行為。如果想知道Proxy的具體使用方法,可參考阮一峰的《 ECMAScript入門 - Proxy 》。
本文將利用Proxy實現(xiàn)前端中3種代理模式的使用場景,分別是:緩存代理、驗證代理、實現(xiàn)私有屬性。
2.1 緩存代理緩存代理可以將一些開銷很大的方法的運算結(jié)果進行緩存,再次調(diào)用該函數(shù)時,若參數(shù)一致,則可以直接返回緩存中的結(jié)果,而不用再重新進行運算。例如在采用后端分頁的表格時,每次頁碼改變時需要重新請求后端數(shù)據(jù),我們可以將頁碼和對應(yīng)結(jié)果進行緩存,當(dāng)請求同一頁時就不用在進行ajax請求而是直接返回緩存中的數(shù)據(jù)。
下面我們以沒有經(jīng)過任何優(yōu)化的計算斐波那契數(shù)列的函數(shù)來假設(shè)為開銷很大的方法,這種遞歸調(diào)用在計算40以上的斐波那契項時就能明顯的感到延遲感。
const getFib = (number) => { if (number <= 2) { return 1; } else { return getFib(number - 1) + getFib(number - 2); } }
現(xiàn)在我們來寫一個創(chuàng)建緩存代理的工廠函數(shù):
const getCacheProxy = (fn, cache = new Map()) => { return new Proxy(fn, { apply(target, context, args) { const argsString = args.join(" "); if (cache.has(argsString)) { // 如果有緩存,直接返回緩存數(shù)據(jù) console.log(`輸出${args}的緩存結(jié)果: ${cache.get(argsString)}`); return cache.get(argsString); } const result = fn(...args); cache.set(argsString, result); return result; } }) }
調(diào)用方法如下:
const getFibProxy = getCacheProxy(getFib); getFibProxy(40); // 102334155 getFibProxy(40); // 輸出40的緩存結(jié)果: 102334155
當(dāng)我們第二次調(diào)用getFibProxy(40)時,getFib函數(shù)并沒有被調(diào)用,而是直接從cache中返回了之前被緩存好的計算結(jié)果。通過加入緩存代理的方式,getFib只需要專注于自己計算斐波那契數(shù)列的職責(zé),緩存的功能使由Proxy對象實現(xiàn)的。這實現(xiàn)了我們之前提到的單一職責(zé)原則。
2.2 驗證代理Proxy構(gòu)造函數(shù)第二個參數(shù)中的set方法,可以很方便的驗證向一個對象的傳值。我們以一個傳統(tǒng)的登陸表單舉例,該表單對象有兩個屬性,分別是account和password,每個屬性值都有一個簡單和其屬性名對應(yīng)的驗證方法,驗證規(guī)則如下:
// 表單對象 const userForm = { account: "", password: "", } // 驗證方法 const validators = { account(value) { // account 只允許為中文 const re = /^[u4e00-u9fa5]+$/; return { valid: re.test(value), error: ""account" is only allowed to be Chinese" } }, password(value) { // password 的長度應(yīng)該大于6個字符 return { valid: value.length >= 6, error: ""password "should more than 6 character" } } }
下面我們來使用Proxy實現(xiàn)一個通用的表單驗證器
const getValidateProxy = (target, validators) => { return new Proxy(target, { _validators: validators, set(target, prop, value) { if (value === "") { console.error(`"${prop}" is not allowed to be empty`); return target[prop] = false; } const validResult = this._validators[prop](value); if(validResult.valid) { return Reflect.set(target, prop, value); } else { console.error(`${validResult.error}`); return target[prop] = false; } } }) }
調(diào)用方式如下
const userFormProxy = getValidateProxy(userForm, validators); userFormProxy.account = "123"; // "account" is only allowed to be Chinese userFormProxy.password = "he"; // "password "should more than 6 character
我們調(diào)用getValidateProxy方法去生成了一個代理對象userFormProxy,該對象在設(shè)置屬性的時候會根據(jù)validators的驗證規(guī)則對值進行校驗。這我們使用的是console.error拋出錯誤信息,當(dāng)然我們也可以加入對DOM的事件來實現(xiàn)頁面中的校驗提示。
2.3 實現(xiàn)私有屬性代理模式還有一個很重要的應(yīng)用是實現(xiàn)訪問限制??偹苤琂avaScript是沒有私有屬性這一個概念的,通常私有屬性的實現(xiàn)是通過函數(shù)作用域中變量實現(xiàn)的,雖然實現(xiàn)了私有屬性,但對于可讀性來說并不好。
私有屬性一般是以_下劃線開頭,通過Proxy構(gòu)造函數(shù)中的第二個參數(shù)所提供的方法,我們可以很好的去限制以_開頭的屬性的訪問。
下面我來實現(xiàn)getPrivateProps這個函數(shù),該函數(shù)的第一個參數(shù)obj是所被代理的對象,第二個參數(shù)filterFunc是過濾訪問屬性的函數(shù),目前該函數(shù)的作用是用來限制以_開頭的屬性訪問。
function getPrivateProps(obj, filterFunc) { return new Proxy(obj, { get(obj, prop) { if (!filterFunc(prop)) { let value = Reflect.get(obj, prop); // 如果是方法, 將this指向修改原對象 if (typeof value === "function") { value = value.bind(obj); } return value; } }, set(obj, prop, value) { if (filterFunc(prop)) { throw new TypeError(`Can"t set property "${prop}"`); } return Reflect.set(obj, prop, value); }, has(obj, prop) { return filterFunc(prop) ? false : Reflect.has(obj, prop); }, ownKeys(obj) { return Reflect.ownKeys(obj).filter(prop => !filterFunc(prop)); }, getOwnPropertyDescriptor(obj, prop) { return filterFunc(prop) ? undefined : Reflect.getOwnPropertyDescriptor(obj, prop); } }); } function propFilter(prop) { return prop.indexOf("_") === 0; }
在上面的getPrivateProps方法的內(nèi)部實現(xiàn)中, Proxy的第二個參數(shù)中我們使用了提供的get,set,has,ownKeys, getOwnPropertyDescriptor這些方法,這些方法的作用其實本質(zhì)都是去最大限度的限制私有屬性的訪問。其中在get方法的內(nèi)部,我們有個判斷,如果訪問的是對象方法使將this指向被代理對象,這是在使用Proxy需要十分注意的,如果不這么做方法內(nèi)部的this會指向Proxy代理。
下面來看一下getPrivateProps的調(diào)用方法,并驗證其代理提供的訪問控制的能力。
const myObj = { public: "hello", _private: "secret", method: function () { console.log(this._private); } }, myProxy = getPrivateProps(myObj, propFilter); console.log(JSON.stringify(myProxy)); // {"public":"hello"} console.log(myProxy._private); // undefined console.log("_private" in myProxy); // false console.log(Object.keys(myProxy)); // ["public", "method"] for (let prop in myProxy) { console.log(prop); } // public method myProxy._private = 1; // Uncaught TypeError: Can"t set property "_private"3 總結(jié)
ES6提供的Proxy可以讓JS開發(fā)者很方便的使用代理模式,聽說Vue 3.0的也會使用Proxy去大量改寫核心代碼。雖然代理模式很方便,但是在業(yè)務(wù)開發(fā)時應(yīng)該注意使用場景,不需要在編寫對象時就去預(yù)先猜測是否需要使用代理模式,只有當(dāng)對象的功能變得復(fù)雜或者我們需要進行一定的訪問限制時,再考慮使用代理。
參考文獻[1] 掘金: 使用 Javascript 原生的 Proxy 優(yōu)化應(yīng)用
[2] Deal With JS: ES6 Features - 10 Use Cases for Proxy
[3] 曾探 JavaScript設(shè)計模式與開發(fā)實踐 [M].r人民郵電出版社
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96351.html
摘要:理解元編程和是屬于元編程范疇的,能介入的對象底層操作進行的過程中,并加以影響。元編程中的元的概念可以理解為程序本身。中,便是兩個可以用來進行元編程的特性。在之后,標(biāo)準(zhǔn)引入了,從而提供比較完善的元編程能力。 導(dǎo)讀 幾年前 ES6 剛出來的時候接觸過 元編程(Metaprogramming)的概念,不過當(dāng)時還沒有深究。今天在應(yīng)用和學(xué)習(xí)中不斷接觸到這概念,比如 mobx 5 中就用到了 Pr...
摘要:虛擬代理延遲執(zhí)行虛擬代理的目的,是將開銷大的運算延遲到需要時再執(zhí)行。 showImg(https://segmentfault.com/img/bVbuitm?w=800&h=600); 代理模式:為一個對象提供一個代用品或占位符,以便控制它的訪問。 當(dāng)我們不方便直接訪問某個對象時,或不滿足需求時,可考慮使用一個替身對象來控制該對象的訪問。替身對象可對請求預(yù)先進行處理,再決定是否轉(zhuǎn)交給...
摘要:什么是適配器模式適配器模式將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。中的適配器模式在前端項目中,適配器模式的使用場景一般有以下三種情況庫的適配參數(shù)的適配和數(shù)據(jù)的適配。 1 什么是適配器模式 適配器模式(Adapter):將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。 在...
摘要:攔截實例作為構(gòu)造函數(shù)調(diào)用的操作,比如。方法等同于,這提供了一種不使用,來調(diào)用構(gòu)造函數(shù)的方法。方法對應(yīng),返回一個布爾值,表示當(dāng)前對象是否可擴展。這是的一個提案,目前轉(zhuǎn)碼器已經(jīng)支持。別名或修飾器在控制臺顯示一條警告,表示該方法將廢除。 Proxy Proxy 這個詞的原意是代理,用在這里表示由它來代理某些操作,可以譯為代理器,即用自己的定義覆蓋了語言的原始定義。ES6 原生提供 Proxy...
閱讀 2381·2021-10-09 09:41
閱讀 3172·2021-09-26 09:46
閱讀 835·2021-09-03 10:34
閱讀 3151·2021-08-11 11:22
閱讀 3365·2019-08-30 14:12
閱讀 711·2019-08-26 11:34
閱讀 3344·2019-08-26 11:00
閱讀 1750·2019-08-26 10:26