代理模式
代理模式是為一個對象提供一個代用品或占位符,以便控制對它的訪問。
生活中有很多的代理模式的場景。例如,明星有經紀人作為代理,老板有秘書作為代理等等,當有事情的時候,會找到經紀人或秘書,再由他們轉達給明星或者老板。
首先通過一個例子來簡單的了解了解,故事是這樣的...
以下故事純屬虛構,不要當真
大家都知道三顧茅廬(不知道的百度一下)吧,諸葛亮何許人也,厲害的不要不要的,名聲在外啊。好巧不巧劉備知道了,劉備心想:“這么厲害的人跟着我,豈不美哉,統一三國起步指日可待”,於是,劉備帶着禮物就去請人家了。正常流程應該這樣:
// 劉備
let bei = {
// 邀請
invite(){
liang.reception('草鞋')
}
}
// 亮
let liang = {
// 收到禮物
reception(gift){
console.log('亮收到禮物:' + gift)
}
}
// 調用方法
bei.invite()
但是呢,事實不是這樣的,諸葛亮心想:“劉備一賣草鞋的就想見我?”,於是呢,劉備只見到了門童:
// 劉備
let bei = {
// 邀請
invite(){
mentong.reception('草鞋')
}
}
// 門童
let mentong = {
// 接收禮物
reception(gift){
console.log('門童收到禮物:' + gift)
// 給諸葛亮
liang.reception('草鞋')
}
}
// 亮
let liang = {
// 接收禮物
reception(gift){
console.log('亮收到禮物:' + gift)
}
}
// 調用方法
bei.invite()
所以,劉備就只能把禮物給了門童,門童在交給了諸葛亮,然后諸葛亮一看,好家伙,草鞋。。。
到此可以看成一個簡單的代理了
保護代理和虛擬代理
保護代理
諸葛亮收到草鞋后也是無語,然后叫來門童告訴他:“以后呢,送草鞋的,你就不用給我了,自己看着處理就好了”,門童心領神會,表示ojbk
// 門童
let mentong = {
// 接收禮物
reception(gift){
console.log('門童收到禮物:' + gift)
if(gift !== '草鞋'){
// 給諸葛亮
liang.reception(gift)
}
}
}
通過代理(門童)來處理一些不必要的東西,過濾掉無用信息,這可以理解為 保護代理
但在 JavaScript 並不容易實現保護代理,因為我們無法判斷誰訪問了某個對象。
虛擬代理
話題又回到劉備這,劉備這連着送禮都兩天了,也見不到人。有人就給他出了個方法。於是啊就去找到門童說:“小兄弟這錢你拿着,你幫我買點禮物給諸葛先生”,門童也是詫異什么時候變聰明了
// 門童
let mentong = {
// 接收禮物
reception(){
// 拿錢去買禮物
let book = new Book()
// 給諸葛亮
liang.reception(book)
}
}
諸葛亮這回挺開心,於是就答應見劉備了(~ ̄▽ ̄)~
這可以理解為 虛擬代理
虛擬代理實現圖片預加載
注:I know 這個例子大家可能都看過了,因為沒有想到更好的例子(想到了更改補上),但是這個例子我會一行一行講解清楚的o( ̄▽ ̄)ブ
平時由於網絡的不佳,導致圖片出來前會有一片空白。所以我們限用一張 loading
圖片占位,在異步方式加載圖片
沒用代理
// 創建一個本體對象
var myImage = (function(){
// 創建標簽
var imgNode = document.createElement( 'img' );
// 添加到頁面
document.body.appendChild( imgNode );
return {
// 設置圖片的src
setSrc: function( src ){
// 更改src
imgNode.src = src;
}
}
})();
myImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
引入代理對象
// 創建一個本體對象
var myImage = (function(){
// 創建標簽
var imgNode = document.createElement( 'img' );
// 添加到頁面
document.body.appendChild( imgNode );
return {
// 設置圖片的src
setSrc: function( src ){
// 更改src
imgNode.src = src;
}
}
})();
// 創建代理對象
var proxyImage = (function(){
// 創建一個新的img標簽
var img = new Image;
// img 加載完成事件
img.onload = function(){
// 調用 myImage 替換src方法
myImage.setSrc( this.src );
}
return {
// 代理設置地址
setSrc: function( src ){
// 預加載 loading
myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
// 賦值正常圖片地址
img.src = src;
}
}
})();
proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
現在我們可以通過 proxyImage
間接地訪問 MyImage
。proxyImage
控制了客戶對 MyImage
的訪問,並
且在此過程中提前把 img
節點的 src
設置為了一張本地的 loading
圖片
現在再來看看不用代理來實現預加載
// 創建一個本體對象
var MyImage = (function(){
// 創建標簽
var imgNode = document.createElement( 'img' );
// 添加到頁面
document.body.appendChild( imgNode );
// 創建一個新的img標簽
var img = new Image;
// img 加載完成
img.onload = function(){
// 替換地址
imgNode.src = img.src;
};
return {
// 設置地址
setSrc: function( src ){
// 本地 loading 圖片地址
imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif';
// 賦值正常圖片地址
img.src = src;
}
}
})();
MyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
現在來看看沒有用代理的代碼
- 違反了單一原則。MyImage除了要負責img節點的設置,還要負責預加載圖片。這導致在處理其中一個職責時會因其強耦合性影響另一個職責。
- 違反了開閉原則。倘若以后要去掉預加載,只能去更改MyImage對象,這不符合開閉原則。
代理和本體接口的一致性
如果有一天我們不再需要預加載,那么就不再需要代理對象,可以選擇直接請求本體。其中關鍵是代理對象和本體都對外提供了 setSrc
方法,在客戶看來,代理對象和本體是一致的, 代理接手請求的過程對於用戶來說是透明的,用戶並不清楚代理和本體的區別這樣做有兩個好處:
- 用戶可以放心地請求代理,他只關心是否能得到想要的結果。
- 在任何使用本體的地方都可以替換成使用代理。(是不是有點里氏替換的味道)
// 預加載
proxyImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
// 不用預加載
myImage.setSrc( 'http:// image.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
緩存代理
再舉一個經典的例子啊(沒想到別的,以后想到了再換(~ ̄▽ ̄)~)
乘積函數
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 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 );
}
})();
proxyMult( 1, 2, 3, 4 ); // 輸出:24
proxyMult( 1, 2, 3, 4 ); // 輸出:24
我們也可以動態創建代理
/**************** 計算乘積 *****************/
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
代理模式包括許多小分類,在JavaScript開發中最常用的是虛擬代理和緩存代理。雖然代理模式非常有用,但我們在編寫業務代碼的時候,往往不需要去預先猜測是否需要使用代理模。當真正發現不方便直接訪問某個對象的時候,再編寫代理也不遲。
目前對於代理模式的理解就這么多,以后有了新的理解會繼續更新的,溜了溜了(~ ̄▽ ̄)~