前言
本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些自己的思考。希望對大家有所幫助。
文章系列
概念
代理模式是為一個物件提供一個代用品或佔位符,以便控制對它的訪問。
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');
複製程式碼
這段程式碼違背了單一職責原則,這個物件同時承擔了載入圖片和預載入圖片兩個職責 同時也違背了開放封閉原則,如果我們以後不需要預載入圖片了,那我們不得不修改整個物件
用虛擬代理改進
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)
複製程式碼