面試官:說(shuō)說(shuō)你對(duì)代理模式的理解?應(yīng)用場(chǎng)景?
一、是什么
代理模式(Proxy Pattern)是為一個(gè)對(duì)象提供一個(gè)代用品或占位符,以便控制對(duì)它的訪問(wèn)
代理模式的關(guān)鍵是,當(dāng)客戶不方便直接訪問(wèn)一個(gè)對(duì)象或者不滿足需要時(shí),提供一個(gè)替身對(duì)象來(lái)控制這個(gè)對(duì)象的訪問(wèn),客戶實(shí)際上訪問(wèn)的是替身對(duì)象
在生活中,代理模式的場(chǎng)景是十分常見(jiàn)的,例如我們現(xiàn)在如果有租房、買房的需求,更多的是去找鏈家等房屋中介機(jī)構(gòu),而不是直接尋找想賣房或出租房的人談。此時(shí),鏈家起到的作用就是代理的作用
二、使用
在ES6中,存在proxy構(gòu)建函數(shù)能夠讓我們輕松使用代理模式:
- const proxy = new Proxy(target, handler);
關(guān)于Proxy的使用可以翻看以前的文章
而按照功能來(lái)劃分,javascript代理模式常用的有:
- 緩存代理
- 虛擬代理
緩存代理
緩存代理可以為一些開(kāi)銷大的運(yùn)算結(jié)果提供暫時(shí)的存儲(chǔ),在下次運(yùn)算時(shí),如果傳遞進(jìn)來(lái)的參數(shù)跟之前一致,則可以直接返回前面存儲(chǔ)的運(yùn)算結(jié)果
如實(shí)現(xiàn)一個(gè)求積乘的函數(shù),如下:
- var muti = function () {
- console.log("開(kāi)始計(jì)算乘積");
- var a = 1;
- for (var i = 0, l = arguments.length; i < l; i++) {
- a = a * arguments[i];
- }
- return a;
- };
現(xiàn)在加入緩存代理,如下:
- var proxyMult = (function () {
- var cache = {};
- return function () {
- var 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
當(dāng)?shù)诙握{(diào)用 proxyMult(1, 2, 3, 4) 時(shí),本體 mult 函數(shù)并沒(méi)有被計(jì)算,proxyMult 直接返回了之前緩存好的計(jì)算結(jié)果
虛擬代理
虛擬代理把一些開(kāi)銷很大的對(duì)象,延遲到真正需要它的時(shí)候才去創(chuàng)建
常見(jiàn)的就是圖片預(yù)加載功能:
未使用代理模式如下:
- let MyImage = (function(){
- let imgNode = document.createElement( 'img' );
- document.body.appendChild( imgNode );
- // 創(chuàng)建一個(gè)Image對(duì)象,用于加載需要設(shè)置的圖片
- let img = new Image;
- img.onload = function(){
- // 監(jiān)聽(tīng)到圖片加載完成后,設(shè)置src為加載完成后的圖片
- imgNode.src = img.src;
- };
- return {
- setSrc: function( src ){
- // 設(shè)置圖片的時(shí)候,設(shè)置為默認(rèn)的loading圖
- imgNode.src = 'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif';
- // 把真正需要設(shè)置的圖片傳給Image對(duì)象的src屬性
- img.src = src;
- }
- }
- })();
- MyImage.setSrc( 'https://xxx.jpg' );
MyImage對(duì)象除了負(fù)責(zé)給img節(jié)點(diǎn)設(shè)置src外,還要負(fù)責(zé)預(yù)加載圖片,違反了面向?qū)ο笤O(shè)計(jì)的原則——單一職責(zé)原則
上述過(guò)程loding則是耦合進(jìn)MyImage對(duì)象里的,如果以后某個(gè)時(shí)候,我們不需要預(yù)加載顯示loading這個(gè)功能了,就只能在MyImage對(duì)象里面改動(dòng)代碼
使用代理模式,代碼則如下:
- // 圖片本地對(duì)象,負(fù)責(zé)往頁(yè)面中創(chuàng)建一個(gè)img標(biāo)簽,并且提供一個(gè)對(duì)外的setSrc接口
- let myImage = (function(){
- let imgNode = document.createElement( 'img' );
- document.body.appendChild( imgNode );
- return {
- //setSrc接口,外界調(diào)用這個(gè)接口,便可以給該img標(biāo)簽設(shè)置src屬性
- setSrc: function( src ){
- imgNode.src = src;
- }
- }
- })();
- // 代理對(duì)象,負(fù)責(zé)圖片預(yù)加載功能
- let proxyImage = (function(){
- // 創(chuàng)建一個(gè)Image對(duì)象,用于加載需要設(shè)置的圖片
- let img = new Image;
- img.onload = function(){
- // 監(jiān)聽(tīng)到圖片加載完成后,給被代理的圖片本地對(duì)象設(shè)置src為加載完成后的圖片
- myImage.setSrc( this.src );
- }
- return {
- setSrc: function( src ){
- // 設(shè)置圖片時(shí),在圖片未被真正加載好時(shí),以這張圖作為loading,提示用戶圖片正在加載
- myImage.setSrc( 'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif' );
- img.src = src;
- }
- }
- })();
- proxyImage.setSrc( 'https://xxx.jpg' );
使用代理模式后,圖片本地對(duì)象負(fù)責(zé)往頁(yè)面中創(chuàng)建一個(gè)img標(biāo)簽,并且提供一個(gè)對(duì)外的setSrc接口;
代理對(duì)象負(fù)責(zé)在圖片未加載完成之前,引入預(yù)加載的loading圖,負(fù)責(zé)了圖片預(yù)加載的功能
上述并沒(méi)有改變或者增加MyImage的接口,但是通過(guò)代理對(duì)象,實(shí)際上給系統(tǒng)添加了新的行為
并且上述代理模式可以發(fā)現(xiàn),代理和本體接口的一致性,如果有一天不需要預(yù)加載,那么就不需要代理對(duì)象,可以選擇直接請(qǐng)求本體。其中關(guān)鍵是代理對(duì)象和本體都對(duì)外提供了 setSrc 方法
三、應(yīng)用場(chǎng)景
現(xiàn)在的很多前端框架或者狀態(tài)管理框架都使用代理模式,用于監(jiān)聽(tīng)變量的變化
使用代理模式代理對(duì)象的訪問(wèn)的方式,一般又被稱為攔截器,比如我們?cè)陧?xiàng)目中經(jīng)常使用 Axios 的實(shí)例來(lái)進(jìn)行 HTTP 的請(qǐng)求,使用攔截器 interceptor 可以提前對(duì) 請(qǐng)求前的數(shù)據(jù) 服務(wù)器返回的數(shù)據(jù)進(jìn)行一些預(yù)處理
以及上述應(yīng)用到的緩存代理和虛擬代理
參考文獻(xiàn)
https://juejin.cn/post/6844903555036364814#heading-2
https://juejin.cn/post/6992510837403418654#heading-7
https://sothx.com/2021/06/26/proxy/