JS-代理模式

介紹

代理是一個對象,它可以用來控制對本體對象的訪問,它與本體對象實現了同樣的接口,代理對象會把所有的調用方法傳遞給本體對象的;代理模式最基本的形式是對訪問進行控制,而本體對象則負責執行所分派的那個對象的函數或者類,簡單的來講本地對象注重的去執行頁面上的代碼,代理則控制本地對象何時被實例化,何時被使用;

優點

1. 可以保護對象,代理對象可以代替本體被實例化,並使其可以被遠程訪問;

2. 優化性能,減少開銷很大的對象

3. 緩存結果

實現

老規矩舉個栗子來解釋一下:小明喜歡上了隔壁班的小紅,但是又不好意思去表白,所以就找舍友小李幫忙去送情書,那小李就是個代理

// 先聲明小紅對象
var girl = function (name) {
    this.name = name;
};

// 這是小明
var ming= function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        console.log("Hi " + girl.name + ", 小明送給你:" + gift);
    }
};

// 小李
var proxyLi = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        (new ming(girl)).sendGift(gift); // 給好基友打call
    }
};

var proxy = new proxyLi(new girl("美麗的小紅"));
proxy.sendGift("一封情書");

//Hi 美麗的小紅, 小明送給你:一封情書

代理模式的分類

代理模式根據職責的不同劃分了不同分類,這裏主要說一下在js中常見的幾種

1.ES6的proxy

// 明星
let star = {
    name: 'CXK',
    age: 25,
    phone: '13910733521'
}

// 經紀人
let agent = new Proxy(star, {
    get: function (target, key) {
        if (key === 'phone') {
            // 保護隱私返回經紀人的手機號
            return '18611112222'
        }
        if (key === 'price') {
            // 明星不報價,經紀人報價
            return 5000000
        }
        return target[key]
    },
    set: function (target, key, val) {
        if (key === 'customPrice') {
            if (val < 4000000) {
                // 最低 400w
                throw new Error('這點錢不太夠')
            } else {
                target[key] = val
                return true
            }
        }
    }
})

// 主辦方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)

// 想自己提供報價(砍價,或者高價爭搶)
agent.customPrice = 8000000
//agent.customPrice = 2000000  // 報錯:這點錢不太夠
console.log('customPrice', agent.customPrice)


CXK
25
18611112222
5000000
customPrice 8000000

2.虛擬代理

2.1使用虛擬代理實現圖片的預加載

在網頁開發中,圖片的預加載是一種比較常用的技術,如果直接給img標籤節點設置src屬性的話,如果圖片比較大的話,或者網速相對比較慢的話,那麼在圖片未加載完之前,圖片會有一段時間是空白的場景,這樣對於用戶體驗來講並不好,那麼這個時候我們可以在圖片未加載完之前我們可以使用一個loading加載圖片來作爲一個佔位符,來提示用戶該圖片正在加載,等圖片加載完後我們可以對該圖片直接進行賦值即可;

// 方案一:不使用代理的預加載圖片函數如下
var myImage = (function(){
    var imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    var img = new Image();
    img.onload = function(){
        imgNode.src = this.src;
    };
    return {
        setSrc: function(src) {
            imgNode.src = "loading.gif";
            img.src = src;
        }
    }
})();
// 調用方式
myImage.setSrc("pic.png");



//方案二:使用代理的預加載圖片函數如下
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);
    };
    return {
        setSrc: function(src) {
            myImage.setSrc("loading.gif");
            img.src = src;
        }
    }
})();
// 調用方式
ProxyImage.setSrc("pic.png");

具體分析一下兩種寫法的優劣

方案一:創建img標籤--->插入img標籤-->創建img對象-->onloading-->返回設置圖片對象

缺點:代碼耦合嚴重,當需求調整不需要預加載圖片的時候,又得修改整個對象

方案二:myImage中創建img標籤--->myImage中插入img標籤-->myImage中返回設置imgNode的src方法-->ProxyImage中創建img對象-->ProxyImage中書寫onload方法-->ProxyImage中返回設置圖片的方法

優點:兩個函數各自負責一件事情,當不需要預加載的時候,直接調用本體方法即可

2.2 使用虛擬代理合並http請求

比如在做後端系統中,有表格數據,每一條數據前面有複選框按鈕,當點擊複選框按鈕時候,需要獲取該id後需要傳遞給給服務器發送ajax請求,服務器端需要記錄這條數據,去請求,如果我們每當點擊一下向服務器發送一個http請求的話,對於服務器來說壓力比較大,網絡請求比較頻繁,但是如果現在該系統的實時數據不是很高的話,我們可以通過一個代理函數收集一段時間內(比如說2-3秒)的所有id,一次性發ajax請求給服務器,相對來說網絡請求降低了, 服務器壓力減少了;


<body>
  <div id="wrapper">
    <input type="checkbox" id="1"></input>1
    <input type="checkbox" id="2"></input>2
    <input type="checkbox" id="3"></input>3
    <input type="checkbox" id="4"></input>4
    <input type="checkbox" id="5"></input>5
    <input type="checkbox" id="6"></input>6
    <input type="checkbox" id="7"></input>7
    <input type="checkbox" id="8"></input>8
    <input type="checkbox" id="9"></input>9
  </div>
</body>

<script type="text/javascript">
  // 模擬http請求
  var synchronousFile = function (id) {
    console.log('開始同步文件,id 爲: ' + id);
  };

  var inputs = document.getElementsByTagName('input')
  var wrapper = document.getElementById('wrapper')
  wrapper.onclick = function (e) {
    if (e.target.tagName === 'INPUT' && e.target.checked) {
      proxySynchronousFile(e.target.id)
    }
  }

  var proxySynchronousFile = (function () {
    var cacheIds = [], // 保存一段時間內需要同步的 ID
    timeId = 0
    return function (id) {
      if (cacheIds.indexOf(id) < 0) {
        cacheIds.push(id)
      }
      clearTimeout(timeId)
      timeId = setTimeout(() => { // 2 秒後向本體發送需要同步的 ID 集合
        synchronousFile(cacheIds.join(','))
        cacheIds = [] // 清空ID集合
      }, 2000)
    }
  })()
</script>

3.緩存代理

 緩存代理的含義就是對第一次運行時候進行緩存,當再一次運行相同的時候,直接從緩存裏面取,這樣做的好處是避免重複一次運算功能,如果運算非常複雜的話,對性能很耗費,那麼使用緩存對象可以提高性能

// 計算乘法
var mult = function(){
    var a = 1;
    for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
        a = a*arguments[i];
    }
    return a;
};
// 計算加法
var plus = function(){
    var a = 0;
    for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
        a += arguments[i];
    }
    return a;
}
// 代理函數
var proxyFunc = function(fn) {
    var cache = {};  // 緩存對象
    return function(){
        var args = Array.prototype.join.call(arguments,',');
        if(args in cache) {
            return cache[args];   // 使用緩存代理
        }
        return cache[args] = fn.apply(this,arguments);
    }
};
var proxyMult = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 緩存取 24

var proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1,2,3,4));  // 10
console.log(proxyPlus(1,2,3,4));  // 緩存取 10

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章