代理模式
小明追 MM 的故事
level01:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
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 );
</script>
</body>
</html>
level02:引入代理 B
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
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,可B立馬給了A
</script>
</body>
</html>
level03:引入機智代理 B
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var Flower = function(){};
var xiaoming = {
sendFlower: function( target){
var flower = new Flower();
target.receiveFlower( flower );
}
};
var B = {
receiveFlower: function( flower ){
A.listenGoodMood(function(){ // 監聽 A 的好心情
A.receiveFlower( flower );
});
}
};
var A = {
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
},
listenGoodMood: function( fn ){
setTimeout(function(){ //假設10秒之後A的心情變好
fn();
}, 10000 );
}
};
xiaoming.sendFlower( B );
//B收到後立馬監聽A的心情。B起到保護作用。
</script>
</body>
</html>
保護代理:代理 B 可以幫助 A過濾掉一些請求,比如送花人中年齡太大的或者沒有寶馬的,這種請求就可以直接在代理 B處被拒絕掉。
虛擬代理:new Flower 也是一個代價昂貴的操作,那麼我們可以把 new Flower 的操作交給代理 B 去執行,代理 B 會選擇在 A 心情好時再執行 new Flower 。
保護代理用於控制不同權限的對象對目標對象的訪問,但在 JavaScript並不容易實現保護代理,因爲我們無法判斷誰訪問了某個對象。而虛擬代理是最常用的一種代理模式
虛擬代理實現圖片預加載
level01:
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
imgNode.src = src;
}
}
})();//myImage是一個函數對象(似java中的類)
myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
缺點:
我們把網速調至 5KB/s,然後通過 MyImage.setSrc 給該 img 節點設置 src ,可以看到,在圖片被加載好之前,頁面中有一段長長的空白時間。
level02:
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( src ){
imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif';
img.src = src;
}
}
})();
MyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
缺點:
1. 違反單一職責原則
上段代碼中的 MyImage 對象除了負責給 img 節點設置 sr外還要負責預加載圖片。我們在處理其中一個職責時,有可能因爲其強耦合性影響另外一個職責的實現。
level03:引入代理對象 proxyImage
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( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;//??????????????????????????????????????
}
}
})();
proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
現在我們通過 proxyImage 間接地訪問 MyImage proxyImage 控制了客戶對 MyImage 的訪問,並且在此過程中加入一些額外的操作,比如在真正的圖片加載好之前,先把 img 節點的 src設置爲一張本地的 loading圖片。
優點:
1. 列表內容
虛擬代理合並 HTTP 請求
level01:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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
<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>
level02:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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
<script>//虛擬代理合並 HTTP 請求
var synchronousFile = function( id ){
console.log( '開始同步文件,id 爲: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [], // 保存一段時間內需要同步的 ID
timer; // 定時器
return function( id ){
cache.push( id );
if ( timer ){ // 保證不會覆蓋已經啓動的定時器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒後向本體發送需要同步的 ID 集合
clearTimeout( timer ); // 清空定時器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};
</script>
</body>
</html>
緩存代理
level01:計算乘積
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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;
};
console.log(mult( 2, 3 )); // 輸出:6
console.log(mult( 2, 3, 4 )); // 輸出:24
</script>
</body>
</html>
level02:緩存代理計算乘積
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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 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 );
}
})();
console.log(proxyMult( 1, 2, 3, 4 )); // 輸出:24
console.log(proxyMult( 1, 2, 3, 4 )); // 輸出:24
</script>
</body>
</html>
用高階函數動態創建代理
level01:計算乘積
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
/**************** 計算乘積 *****************/
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 ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( 1, 2, 3, 4 ) ); // 輸出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 輸出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 輸出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 輸出:10
</script>
</body>
</html>
小結
代理模式包括許多小分類,在 JavaScript開發中最常用的是虛擬代理和緩存代理。雖然代理模式非常有用,但我們在編寫業務代碼的時候,往往不需要去預先猜測是否需要使用代理模式。當真正發現不方便直接訪問某個對象的時候,再編寫代理也不遲。