第6章代理模式
6.1 第一個例子--小明追MM的故事
var Flower = function () {};
var xiaoming = {
sendFlower : function(target){
var flower = new Flower();
target.receiveFlower(flower);
}
}
var A = {
receiveFlower:function(flower){
console.log('收到花'+flower);
}
}
xiaoming.sendFlower(A);
引入代理B
var Flower = function () {};
var xiaoming = {
sendFlower : function(target){
var flower = new Flower();
target.receiveFlower(flower);
}
}
var B = {
receiveFlower:function(flower){
A.receiveFlower(flower)
}
}
var A = {
receiveFlower:function(flower){
console.log('收到花'+flower);
}
}
xiaoming.sendFlower(B);
B會監(jiān)聽A的心情變化
var Flower = function () {};
var xiaoming = {
sendFlower : function(target){
var flower = new Flower();
target.receiveFlower(flower);
}
}
var B = {
receiveFlower:function(flower){
A.listenGoodMood(function(){
A.receiveFlower(flower)
});
}
}
var A = {
receiveFlower:function(flower){
console.log('收到花'+flower);
},
listenGoodMood:function(fn){
setTimeout(function(){
fn();
},3000);
}
}
xiaoming.sendFlower(B);
6.2 保護(hù)代理和虛擬代理
比如送花的人中年齡太大或者沒有寶馬懦趋,這種請求就可以直接在代理B處被拒絕掉,這種代理叫保護(hù)代理。
白臉A繼續(xù)保持良好的女神形象,不希望直接拒絕任何人,于是找了黑臉B來控制對A的訪問
代理B會在A心情好時再執(zhí)行new Flower,這叫虛擬代理,它會把一些開銷很大的對象,延遲到真正需要它的時候才去創(chuàng)建
var B = {
receiveFlower:function(){
A.listenGoodMood(function(){
var flower = new flower();
A.receiveFlower(flower);
});
}
}
在Javascript中并不容易實現(xiàn)保護(hù)代理枫振,因為我們無法判斷誰訪問了某個對象。而虛擬代理是最常用的一種代理模式萤彩,本章主要討論的也是虛擬代理粪滤。
6.3 虛擬代理實現(xiàn)圖片預(yù)加載
var myImage = (function(){
// body...
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc : function(src){
imgNode.src = src;
}
}
})();
var proxyImage = (function(){
var img = new Image;
img.load = function(){
myImage.setSrc(this.src);
}
return {
setSrc:function(src){
myImage.setSrc('file://jdjisj.gif');
img.src = src;
}
}
})()
myImage.setSrc('http:wwjjfjds.jpg');
6.4 代理的意義
不用代理的預(yù)加載圖片函數(shù)實現(xiàn)
var myImage = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
var img = new Image;
img.onload = function(){
imgNode.src = img.src;
};
return {
setSrc:function(){
imgNode.src = 'file://dsd.gif';
img.src = src;
}
}
})()
MyImage.setSrc('http://dsadsd.jpg');
面向?qū)ο笤O(shè)計的原則--單一職責(zé)原則
面向?qū)ο笤O(shè)計鼓勵將行為分布到細(xì)粒度的對象之中,如果一個對象承擔(dān)的職責(zé)過多雀扶,等于把這些職責(zé)耦合到了一起杖小,這中耦合會導(dǎo)致脆弱和低內(nèi)聚的設(shè)計。當(dāng)發(fā)生變化時愚墓,設(shè)計可能會遭到意外的破環(huán)予权。
面向?qū)ο蟪绦蛑校蠖鄶?shù)情況下浪册,若違反其他任何原則扫腺,同時將違反開放-封閉原則。這時刪預(yù)加載代碼就不得不動MyImage對象了
代理負(fù)責(zé)預(yù)加載圖片完成后把請求重新交給本體村象。
不需要預(yù)加載了只需要改成請求本體而不是請求代理對象即可
6.5 代理和本體接口的一致性
代理對象和本體都對外提供了setSrc方法笆环,在客戶看來攒至,代理對象和本體是一致的,代理接手請求的過程對于用戶來說是透明的躁劣,用戶并不清楚代理和本體的區(qū)別嗓袱。
用戶可以放心的請求代理,他只關(guān)心是否能得到想要的結(jié)果
在任何使用本體的地方都可以替換成使用代理
在Javascript這種語言中习绢,我們有時通過鴨子類型來檢測代理和本體是否都實現(xiàn)了setSrc方法,另外大多數(shù)時候甚至不做檢測蝙昙,全部依賴程序員的自覺性
沒有接口的世界
如果代理對象和本體對象都為一個函數(shù)(函數(shù)也是對象)闪萄,函數(shù)必然都能被執(zhí)行,則可以認(rèn)為它們也具有一致的“接口”
var myImage = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return function(src){
imgNode.src = src;
}
})();
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage(this.src);
}
return function(src){
myImage('file:fsfdf.gif');
img.src = src;
}
})();
proxyImage('http://sds.jpg');
6.6 虛擬代理合并HTTP請求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="checkbox" name="" id="1">1
<input type="checkbox" name="" id="2">2
<input type="checkbox" name="" id="3">3
<input type="checkbox" name="" id="4">4
<input type="checkbox" name="" id="5">5
<input type="checkbox" name="" id="6">6
<input type="checkbox" name="" id="7">7
<input type="checkbox" name="" id="8">8
<input type="checkbox" name="" id="9">9
<script>
var synchronousFile = function(id){
console.log('開始同步文件,id為:'+'id');
}
var checkbox = document.getElementsByTagName('input');
for(var i = 0,c; c = checkbox[i++];){
c.onclick = function(){
if (this.checked === true) {
synchronousFile(this.id);
}
}
}
</script>
</body>
</html>
如此頻繁的網(wǎng)絡(luò)請求將會帶來相當(dāng)大的開銷
var synchronousFile = function(id){
console.log('開始同步文件奇颠,id為:' + id);
}
var proxySynchronousFile = (function(){
var cache = [],timer;
return function(id){
cache.push(id);
if(timer){
return;
}
timer = setTimeout(function(){
synchronousFile(cache.join(','));
clearTimeout(timer);
timer = null;
cache.length = 0;
},2000)
}
})();
var checkbox = document.getElementsByTagName('input');
for(var i = 0,c;c = checkbox[i++]){
c.onclick = function(){
if (this.checked ===true) {
proxySynchronousFile(this.id);
}
}
}
6.7 虛擬代理在惰性加載中的應(yīng)用
var miniConsole = (function(){
var cache = [];
var handler = function(ev){
if(ev.keyCode === 113){
var script = document.createElement('script');
script.onload = function(){
for(var i = 0, fn;fn = cache[i++];){
fn();
}
}
script.src = 'miniConsole.js';
document.getElementsByTagName('head')[0].appendChild(script);
document.body.removeEventListener('keydown',handler);//只加載一次miniConsole.js
}
};
document.body.addEventListener('keydown',handler,false);
return {
log:function(){
var args = arguments;
cache.push(function(){
return miniConsole.log.apply(miniConsole,args);
});
}
}
})();
miniConsole.log(11);
//miniConsole.js代碼
miniConsole = {
log:function(){
//真正代碼略
console.log(Array.prototype.join.call(arguments));
}
}
6.8 緩存代理
6.8.1 緩存代理的例子--計算乘積
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
//加入緩存代理函數(shù)
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);
}
})();
var a = proxyMult(1,2,3,4); //24
var b = proxyMult(1,2,3,4); //24
console.log(a,b)
開始計算
24 24
6.8.2 緩存代理用于ajax異步請求數(shù)據(jù)
已經(jīng)拉取的數(shù)據(jù)在某個地方被緩存之后败去,下次再請求同一頁的時候,便可以直接使用之前的數(shù)據(jù)烈拒。
這里也可以引入緩存代理圆裕,實現(xiàn)方式跟計算乘積的例子差不多,唯一不同的是荆几,請求是個異步操作吓妆,我們無法把 計算結(jié)果放在代理對象的緩存中,而是通過回調(diào)的方式
6.9 用高階函數(shù)動態(tài)創(chuàng)建代理
var mult = function(){
var a = 1;
for(var i = 0,l = arguments.length;i<l;i++){
a = a*arguments[i];
}
return a;
}
var plus = function(){
var a =0;
for(var i = 0,l=arguments.length;i<l;i++){
a = a + arguments[i]
}
return a;
}
var createProxyFactory = 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 = createProxyFactory(mult);
var proxyPlus = createProxyFactory(plus);
console.log(proxyMult(1,2,3,4));
console.log(proxyMult(1,2,3,4));
console.log(proxyPlus(1,2,3,4));
console.log(proxyPlus(1,2,3,4));
6.10 其他代理模式
防火墻模式:控制網(wǎng)絡(luò)資源的訪問吨铸,保護(hù)主題不讓“壞人”接近
遠(yuǎn)程代理:為一個對象在不同的地址空間提供局部代表行拢,在JAVA中,遠(yuǎn)程代理可以是另一個虛擬機(jī)中的對象
保護(hù)代理:用于對象應(yīng)該有不同訪問權(quán)限的情況诞吱。
智能引用的次數(shù):取代了簡單的指針舟奠,它訪問對象時執(zhí)行一些附加操作,比如計算對象被引用的次數(shù)房维。
寫時復(fù)制代理:通常用于復(fù)制一個龐大對象的情況沼瘫。寫時復(fù)制代理延遲了復(fù)制的過程,當(dāng)對象被真正修改時咙俩,才對它進(jìn)行復(fù)制操作耿戚。寫時復(fù)制代理是虛擬代理的一種變體,DLL(操作系統(tǒng)中的動態(tài)鏈接庫)是其典型運用場景