js設計模式--代理模式

前言

本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些本身的思考。但願對你們有所幫助。javascript

文章系列

js設計模式--單例模式html

js設計模式--策略模式java

js設計模式--代理模式es6

概念

代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。segmentfault

UML類圖

場景

好比,明星都有經紀人做爲代理。若是想請明星來辦一場商業演出,只能聯繫他的經紀人。經紀人會把商業演出的細節和報酬都談好以後,再把合同交給明星籤。設計模式

分類

保護代理

於控制不一樣權限的對象對目標對象的訪問,如上面明星經紀人的例子
複製代碼

虛擬代理

把一些開銷很大的對象,延遲到真正須要它的時候纔去建立。
如短期內發起不少個http請求,咱們能夠用虛擬代理實現必定時間內的請求統一發送
複製代碼

優缺點

優勢

1. 能夠保護對象
2. 優化性能,減小開銷很大的對象
3. 緩存結果
複製代碼

例子

圖片預加載

加載一張圖片

var myImage = (function () {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
      setSrc: function (src) {
        imgNode.src = src;
      }
    }
  })();
  myImage.setSrc('https://segmentfault.com/img/bVbmvnB?w=573&h=158');

複製代碼

想象一下,若是咱們的圖片很大,用戶就會看到頁面很長一段時間是空白 咱們能夠想到的改進是圖片加載完成以前都展現loading圖片緩存

加個loading圖片

var myImage = (function () {
  var imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  var img = new Image()
  img.onload = () => {
    // 模擬圖片加載
    setTimeout(() => {
      imgNode.src = img.src
    }, 1000)
  }
  return {
    setSrc: function (src) {
      img.src = src
      imgNode.src = 'https://content.igola.com/static/WEB/images/other/loading-searching.gif';
    }
  }
})();
myImage.setSrc('https://segmentfault.com/img/bVbmvnB?w=573&h=158');

複製代碼

這段代碼違背了單一職責原則,這個對象同時承擔了加載圖片和預加載圖片兩個職責 同時也違背了開放封閉原則,若是咱們之後不須要預加載圖片了,那咱們不得不修改整個對象app

用虛擬代理改進

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(img.src)
  }
  return {
    setSrc: function (src) {
      img.src = src
      myImage.setSrc('https://content.igola.com/static/WEB/images/other/loading-searching.gif')
  }
  }
})()


proxyImage.setSrc('https://segmentfault.com/img/bVbmvnB?w=573&h=158');
複製代碼

注意:咱們的代理和本體接口要保持一致性,如上面proxyImage和myImage都返回一個包含setSrc方法的對象。居於這點咱們寫代理的時候也有跡可循。post

虛擬代理合並HTTP請求

簡單的實現

<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') { synchronousFile(e.target.id) } } </script>
複製代碼

缺點很明顯:每點一次就發送一次http請求性能

改進

<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 = [], timeId = 0 return function (id) { if (cacheIds.indexOf(id) < 0) { cacheIds.push(id) } clearTimeout(timeId) timeId = setTimeout(() => { synchronousFile(cacheIds.join(',')) cacheIds = [] }, 1000) } })() </script>
複製代碼

緩存代理-計算乘積

粗糙的實現

var mult = function () {
    console.log('開始計算乘積');
    var a = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
      a = a * arguments[i];
    }
    return a;
  };
  mult(2, 3); // 輸出:6
  mult(2, 3, 4); // 輸出:24

複製代碼

改進

var mult = function () {
  console.log('開始計算乘積');
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a = a * arguments[i];
  }
  return a;
};
// mult(2, 3); // 輸出:6
// mult(2, 3, 4); // 輸出:24

var proxyMult = (function() {
  var cache = {}
  return function () {
    let id = Array.prototype.join.call(arguments, ',')
    if (cache[id]) {
      return cache[id]
    } else {
      return cache[id] = mult.apply(this, arguments)
    }
  }
})()

proxyMult(2, 3); // 輸出:6
proxyMult(2, 3); // 輸出:6
複製代碼

咱們如今但願加法也可以緩存

再改進

var mult = function () {
  console.log('開始計算乘積');
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a = a * arguments[i];
  }
  return a;
};

var plus = function () {
  console.log('開始計算和');
  var a = 0;
  for (var i = 0, l = arguments.length; i < l; i++) {
    a = a + arguments[i];
  }
  return a;
};


// mult(2, 3); // 輸出:6
// mult(2, 3, 4); // 輸出:24

var createProxyFactory = function (fn) {
  var cache = {}
  return function () {
    let id = Array.prototype.join.call(arguments, ',')
    if (cache[id]) {
      return cache[id]
    } else {
      return cache[id] = fn.apply(this, arguments)
    }
  }
}

var proxyMult = createProxyFactory(mult),
  proxyPlus = createProxyFactory(plus);
proxyMult(1, 2, 3, 4) // 輸出:24
proxyMult(1, 2, 3, 4) // 輸出:24
proxyPlus(1, 2, 3, 4) // 輸出:10
proxyPlus(1, 2, 3, 4) // 輸出:10
複製代碼

es6的代理模式

基於類實現

class Car {
    drive() {
        return "driving";
    };
}

class CarProxy {
    constructor(driver) {
        this.driver = driver;
    }
    drive() {
        return  ( this.driver.age < 18) ? "too young to drive" : new Car().drive();
    };
}

class Driver {
    constructor(age) {
        this.age = age;
    }
}
複製代碼

基於Proxy實現

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

// 經紀人
let agent = new Proxy(star, {
    get: function (target, key) {
        if (key === 'phone') {
            // 返回經紀人本身的手機號
            return '18611112222'
        }
        if (key === 'price') {
            // 明星不報價,經紀人報價
            return 120000
        }
        return target[key]
    },
    set: function (target, key, val) {
        if (key === 'customPrice') {
            if (val < 100000) {
                // 最低 10w
                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 = 150000
// agent.customPrice = 90000 // 報錯:價格過低
console.log('customPrice', agent.customPrice)
複製代碼
相關文章
相關標籤/搜索