當我們提出一個門面,我們要向這個世界展現(xiàn)的是一個外觀,這一外觀可能藏匿著一種非常與眾不同的真實。這就是我們即將要回顧的模式背后的靈感——門面模式。這一模式提供了面向一種更大型的代碼體提供了一個的更高級別的舒適的接口,隱藏了其真正的潛在復雜性。把這一模式想象成要是呈現(xiàn)給開發(fā)者簡化的API,一些總是會提升使用性能的東西。
門面是一種經(jīng)常可以在Javascript庫中看到的結構性模式,像在jQuery中,盡管一種實現(xiàn)可能支持帶有廣泛行為的方法,但僅僅只有這些方法的“門面”或者說被限制住的抽象才會公開展現(xiàn)出來供人們所使用。
這允許我們直接同門面,而不是同幕后的子系統(tǒng)交互。不論何時我們使用jQuery的$(el).css或者$(el).animate()方法,我們實際上都是在使用一個門面——更加簡單的公共接口讓我們避免為了使得行為工作起來而不得不去手動調用jQuery核心的內置方法。這也避免了手動同DOM API交互和維護狀態(tài)變量的需要。
應該考慮對jQuery的核心方法做一層中間抽象。對于開發(fā)者來說更直接的負擔是DOM API,而門面使得jQuery使用起來如此的容易。
為了在我們所學的基礎上進行構建,門面模式同時需要簡化一個類的接口,和把類同使用它的代碼解耦。這給予了我們使用一種方式直接同子系統(tǒng)交互的能力,這一方式有時候會比直接訪問子系統(tǒng)更加不容易出錯。門面的優(yōu)勢包括易用,還有常常實現(xiàn)起這個模式來只是一小段路,不費力。
讓我們通過實踐來看看這個模式。這是一個沒有經(jīng)過優(yōu)化的代碼示例,但是這里我們使用了一個門面來簡化跨瀏覽器事件監(jiān)聽的接口。我們創(chuàng)建了一個公共的方法來實現(xiàn),此方法 能夠被用在檢查特性的存在的代碼中,以便這段代碼能夠提供一種安全和跨瀏覽器兼容方案。
var addMyEvent = function( el,ev,fn ){
if( el.addEventListener ){
el.addEventListener( ev,fn, false );
}else if(el.attachEvent){
el.attachEvent( "on" + ev, fn );
} else{
el["on" + ev] = fn;
}
};
我們都熟知jQuery的$(document).ready(..),使 用了一種類似的方式。在內部,這實際上是考一個叫做bindReady()的方法來驅動的,它做了一些這樣的事:
bindReady: function() {
...
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
document.attachEvent( "onreadystatechange", DOMContentLoaded );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
這是門面的另外一個例子,其它人只需要使用被$(document).ready(...)有限暴露的簡單接口,而更加復雜的實現(xiàn)被從視野中隱藏了。
門面不僅僅只被用在它們自己身上,它們也能夠被用來同其它的模式諸如模塊模式進行集成。如我們在下面所看到的,我們模塊模式的實體包含許多被定義為私有的方法。門面則被用來提供訪問這些方法的更加簡單的API:
var module = (function() {
var _private = {
i:5,
get : function() {
console.log( "current value:" + this.i);
},
set : function( val ) {
this.i = val;
},
run : function() {
console.log( "running" );
},
jump: function(){
console.log( "jumping" );
}
};
return {
facade : function( args ) {
_private.set(args.val);
_private.get();
if ( args.run ) {
_private.run();
}
}
};
}());
// Outputs: "current value: 10" and "running"
module.facade( {run: true, val:10} );
在這個示例中,調用module.facade()將會觸發(fā)一堆模塊中的私有方法。但再一次,用戶并不需要關心這些。我們已經(jīng)使得對用戶而言不需要擔心實現(xiàn)級別的細節(jié)就能消受一種特性。
門面一般沒有多少缺陷,但是性能是值得注意的問題。也就是說,需要確定門面在為我們提供實現(xiàn)的同時是否為我們帶來了隱性的消耗,如果是這樣的話,那么這種消耗是否合理。回到jQuery庫,我們都知道getElementById('identifier')和$("#identifier")都能夠被用來借助ID查找頁面上的一個元素。
然而你是否知道getElementById()擁有更高數(shù)量級的速度呢?來瞧瞧這個jsPerf的測試,看一看在每一個瀏覽器級別的結果:http://jsperf.com/getelementbyid-vs-jquery-id。當然現(xiàn)在,我們應該牢記在心的是jQuery(和Sizzle-它的的選擇器引擎)在幕后對我們的查詢(而這返回的是一個jQuery對象,并不是一個DOM節(jié)點)做了更大量的優(yōu)化。
這個特定的門面模式所面臨的挑戰(zhàn)就是,為了提供一種優(yōu)雅的接受和轉換多種查詢類型的選擇器功能,就會有在抽象上的隱性成本。用戶并不需要訪問jQuery.getById("identifier")或者jQuery.getbyClass("identifier")等等方法。那就是說,在性能上權衡已經(jīng)通過了多年的實踐考量,并且?guī)Я薺Query的成功,一個實際上為團隊工作得很好的門面。
當使用這個模式的時候,嘗試了解任何有關性能上面的消耗,要知道它們是否值得以抽象的級別被提供出來調用。
更多建議: