js設計模式--代理模式

凹瓶發表於2019-01-03

前言

本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些自己的思考。希望對大家有所幫助。

文章系列

js設計模式--單例模式

js設計模式--策略模式

js設計模式--代理模式

概念

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

UML類圖

js設計模式--代理模式

場景

比如,明星都有經紀人作為代理。如果想請明星來辦一場商業演出,只能聯絡他的經紀人。經紀人會把商業演出的細節和報酬都談好之後,再把合同交給明星籤。

分類

保護代理

於控制不同許可權的物件對目標物件的訪問,如上面明星經紀人的例子
複製程式碼

虛擬代理

把一些開銷很大的物件,延遲到真正需要它的時候才去建立。
如短時間內發起很多個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');

複製程式碼

這段程式碼違背了單一職責原則,這個物件同時承擔了載入圖片和預載入圖片兩個職責 同時也違背了開放封閉原則,如果我們以後不需要預載入圖片了,那我們不得不修改整個物件

用虛擬代理改進


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方法的物件。居於這點我們寫代理的時候也有跡可循。

虛擬代理合並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)
複製程式碼

相關文章