1、定義javascript
代理是一個對象,它能夠用來控制對另外一個對象的訪問。它與另外那個對象實現了一樣的接口,而且會把任何方法調用傳遞給那個對象。另外那個對象一般稱爲本體。代理能夠代替其實體被實例化,並使其可被遠程訪問。它還能夠把本體的實例化推遲到真正須要的時候,對於實例化比較費時的本體,或者因尺寸較大以致於不用時不易保存在內存中的本體,這特別有用。在處理那些須要較長時間才能把數據載入用戶界面的類時,代理也大有裨益。html
代理模式最基本的形式是對訪問進行控制。代理對象和另外一個對象(本體)實現的是一樣的接口。實際上工做仍是本體在作,它纔是負責執行所分派的任務的那個對象或類。代理對象所作的不外乎節制對本體的訪問。要注意,代理對象並不會在另外一對象的基礎上添加方法或修改方法(就像裝飾者那樣),也不會簡化那個對象的接口(就像門面元素那樣)。它實現的接口與本體徹底相同,全部對它進行的方法調用都會被傳遞給本體。java
2、代理如何控制對本體的訪問程序員
2.1 直接代理web
var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']); var PublicLibrary = function(books) { // implements Library this.catalog = {}; for(var i = 0, len = books.length; i < len; i++) { this.catalog[books[i].getIsbn()] = { book: books[i], available: true }; } }; PublicLibrary.prototype = { findBooks: function(searchString) { var results = []; for(var isbn in this.catalog) { if(!this.catalog.hasOwnProperty(isbn)) continue; if(searchString.match(this.catalog[isbn].getTitle()) || searchString.match(this.catalog[isbn].getAuthor())) { results.push(this.catalog[isbn]); } } return results; }, checkoutBook: function(book) { var isbn = book.getIsbn(); if(this.catalog[isbn]) { if(this.catalog[isbn].available) { this.catalog[isbn].available = false; return this.catalog[isbn]; } else { throw new Error('PublicLibrary: book ' + book.getTitle() + ' is not currently available.'); } } else { throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.'); } }, returnBook: function(book) { var isbn = book.getIsbn(); if(this.catalog[isbn]) { this.catalog[isbn].available = true; } else { throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.'); } } };
var PublicLibraryProxy = function(catalog) { // implements Library this.library = new PublicLibrary(catalog); }; PublicLibraryProxy.prototype = { findBooks: function(searchString) { return this.library.findBooks(searchString); }, checkoutBook: function(book) { return this.library.checkoutBook(book); }, returnBook: function(book) { return this.library.returnBook(book); } };
PublicLibraryProxy和PublicLibrary實現了一樣的接口和同一批方法。這個類在實例化時會建立一個PublicLibrary實例並將其做爲屬性保存。若是調用該類的某個方法,它會經過這個屬性在其PublicLibrary實例上調用同名方法。這種類型的代理也能夠經過檢查本體的接口併爲每個方法建立對應方法這樣一種方式動態地建立。設計模式
2.2 虛擬代理模塊化
var PublicLibraryVirtualProxy = function(catalog) { // implements Library this.library = null; this.catalog = catalog; // Store the argument to the constructor. }; PublicLibraryVirtualProxy.prototype = { _initializeLibrary: function() { if(this.library === null) { this.library = new PublicLibrary(this.catalog); } }, findBooks: function(searchString) { this._initializeLibrary(); return this.library.findBooks(searchString); }, checkoutBook: function(book) { this._initializeLibrary(); return this.library.checkoutBook(book); }, returnBook: function(book) { this._initializeLibrary(); return this.library.returnBook(book); } };
PublicLibraryVirtualProxy會把構造函數的參數保存起來,直到有方法被調用時才真正執行本體的實例化。這樣一來,若是圖書館對象一直沒有被調用到,那麼它就不會被建立出來。虛擬代理一般具備某種能觸發本體的實例化的事件。在本例中,方法調用就是觸發元素。函數
3、與裝飾者模式的區別fetch
共同點:二者都要對其餘對象進行包裝,都要事先與被包裝對象相同的接口,並且都要把方法調用傳遞給被包裝對象。
不一樣點:
1> 裝飾者會對被包裝對象的功能進行修改或擴充,而代理只不過是控制對它的訪問。除了有時可能會添加一些控制代碼以外,代理並不會對傳遞給本體的方法調用進行修改。而裝飾者就是爲修改方法而生的。
2> 在裝飾者模式中,被包裝對象的實例化過程是徹底獨立的。這個對象建立出來以後,你能夠隨意爲其包裹上一個或更多裝飾者。而在代理模式中,被包裝對象的實例化時代理的實例化過程的一部分。在某些類型的虛擬代理中,這種實例化受到嚴格控制,它必須在代理內部進行。
3> 代理不會像裝飾者那樣相互包裝。它們一次只使用一個。優化
4、使用場合
虛擬代理是一個對象,用於控制對一個建立開銷昂貴的資源的訪問。虛擬代理是一種優化模式。若是有些對象須要使用大量內存保存其數據,而你並不須要在實例化完成以後訪問這些數據,或者,其構造函數須要進行大量計算那就應該使用虛擬代理將設置開銷的產生推遲到真正須要使用數據的時候。代理能夠在設置的進行過程當中提供相似於「正在加載.....」這樣的消息,這能夠造成一個反應積極的用戶界面,以避免讓用戶面對一個沒有任何反饋的空白頁面發呆,不知道究竟發生了什麼事。
遠程代理則沒有這樣清楚的用例。若是須要訪問某種遠程資源的話,那麼最好是用一個類或獨享來包裝它,而不是一遍又一遍地手工設置XMLHttpRequest對象。問題在於應該用什麼類型的對象來包裝這個資源呢?這主要是個命名問題。若是包裝對象實現了遠程資源的全部方法,那麼它就是一個遠程代理。若是它會在運行期間增添一些方法,那它就是一個裝飾者。若是它簡化了該遠程資源(或多個遠程資源)的接口,那它就是一個門面。遠程代理是一種結構型模式,它提供了一個訪問位於其餘環境中的資源的原生JavaScript API。
下面引入包裝web服務的通用包裝模式的代理——
var WebserviceProxy = function() { this.xhrHandler = XhrManager.createXhrHandler(); }; WebserviceProxy.prototype = { _xhrFailure: function(statusCode) { throw new Error('StatsProxy: Asynchronous request for stats failed.'); }, _fetchData: function(url, dataCallback, getVars) { var that = this; var callback = { success: function(responseText) { var obj = eval('(' + responseText + ')'); dataCallback(obj); }, failure: that._xhrFailure }; var getVarArray = []; for(varName in getVars) { getVarArray.push(varName + '=' + getVars[varName]); } if(getVarArray.length > 0) { url = url + '?' + getVarArray.join('&'); } xhrHandler.request('GET', url, callback); } }; /* StatsProxy class, using WebserviceProxy. */ var StatsProxy = function() {}; // implements PageStats extend(StatsProxy, WebserviceProxy); /* Implement the needed methods. */ StatsProxy.prototype.getPageviews = function(callback, startDate, endDate, page) { this._fetchData('/stats/getPageviews/', callback, { 'startDate': startDate, 'endDate': endDate, 'page': page }); }; StatsProxy.prototype.getUniques = function(callback, startDate, endDate, page) { this._fetchData('/stats/getUniques/', callback, { 'startDate': startDate, 'endDate': endDate, 'page': page }); }; StatsProxy.prototype.getBrowserShare = function(callback, startDate, endDate, page) { this._fetchData('/stats/getBrowserShare/', callback, { 'startDate': startDate, 'endDate': endDate, 'page': page }); }; StatsProxy.prototype.getTopSearchTerms = function(callback, startDate, endDate, page) { this._fetchData('/stats/getTopSearchTerms/', callback, { 'startDate': startDate, 'endDate': endDate, 'page': page }); }; StatsProxy.prototype.getMostVisitedPages = function(callback, startDate,endDate) { this._fetchData('/stats/getMostVisitedPages/', callback, { 'startDate': startDate, 'endDate': endDate }); };
5、優點
遠程代理的好處在於,能夠把遠程資源當作本地JavaScript對象使用。它減小了爲遠程訪問資源而不得不編寫的粘合性代碼的數量,而且爲此提供了單一的接口。若是遠程資源提供的API發生了改變,須要修改的代碼只有一處。它還把與遠程資源相關的全部數據統一保存在一個地方,其中包括資源的URL、數據格式、命令和相應的結構。若是須要訪問多個WEB服務,那麼能夠先建立一個抽象的通用遠程代理類,而後針對每一種要訪問的web服務派生出一個子類。
虛擬代理的好處在於:能夠把大對象的實例化推遲到其餘元素加載完畢以後。若是虛擬代理包裝的資源沒有被用到,那麼根本就不會被加載。不用操心實例化開銷的問題。
6、劣勢
代理能夠掩蓋了大量複雜行爲。
對於遠程代理而言,其背後的複雜行爲包括髮出XHR請求、等待響應、對響應接口進行解析以及輸出收到的數據。在使用遠程代理的程序員眼裏,它可能就像一個本地資源,但訪問它花的時間卻比訪問本地資源要出幾個數量級。並且,它須要和毀掉函數結合使用,由於讓方法直接放回結果是行不通的,這給代碼增長了必定的複雜性,而且進一步拆穿了其訪問資源的假象。這裏的問題也能經過精心編撰的程序文檔來消除(至少也能夠減輕其不利影響)。
對於虛擬代理也是如此。它掩蓋了推遲本體的實例化的邏輯。使用這種代理的程序員並不清楚又那些操做會觸發對象的實例化。
綜上,由於代理與其本體徹底能夠互換,若是沒有使人信服的理由使用代理的話(或者下降代碼的冗餘程度,或者提升其模塊化的程度,或運行效率),最好仍是選擇直接訪問本體這種簡單得多的方法。
源自:JavaScript設計模式(人民郵電出版社)——第十二章,裝飾者模式