jQuery中的編程范式詳解
本文詳細分析了jQuery中的編程范式。分享給大家供大家參考。具體如下:
瀏覽器前端編程的面貌自2005年以來已經發(fā)生了深刻的變化,這并不簡單的意味著出現(xiàn)了大量功能豐富的基礎庫,使得我們可以更加方便的編寫業(yè)務代碼,更重要的是我們看待前端技術的觀念發(fā)生了重大轉變,明確意識到了如何以前端特有的方式釋放程序員的生產力。這里將結合jQuery源碼的實現(xiàn)原理,對javascript中涌現(xiàn)出的編程范式和常用技巧作一簡單介紹。
1. AJAX: 狀態(tài)駐留,異步更新
首先來看一點歷史。
A. 1995年Netscape公司的Brendan Eich開發(fā)了javacript語言,這是一種動態(tài)(dynamic)、弱類型(weakly typed)、基于原型(prototype-based)的腳本語言。
B. 1999年微軟IE5發(fā)布,其中包含了XMLHTTP ActiveX控件。
C. 2001年微軟IE6發(fā)布,部分支持DOM level 1和CSS 2標準。
D. 2002年Douglas Crockford發(fā)明JSON格式。
至此,可以說Web2.0所依賴的技術元素已經基本成形,但是并沒有立刻在整個業(yè)界產生重大的影響。盡管一些“頁面異步局部刷新”的技巧在程序員中間秘密的流傳,甚至催生了bindows這樣龐大臃腫的類庫,但總的來說,前端被看作是貧瘠而又骯臟的沼澤地,只有后臺技術才是王道。到底還缺少些什么呢?
當我們站在今天的角度去回顧2005年之前的js代碼,包括那些當時的牛人所寫的代碼,可以明顯的感受到它們在程序控制力上的孱弱。并不是說2005年之前的js技術本身存在問題,只是它們在概念層面上是一盤散沙,缺乏統(tǒng)一的觀念,或者說缺少自己獨特的風格, 自己的靈魂。當時大多數(shù)的人,大多數(shù)的技術都試圖在模擬傳統(tǒng)的面向對象語言,利用傳統(tǒng)的面向對象技術,去實現(xiàn)傳統(tǒng)的GUI模型的仿制品。
2005年是變革的一年,也是創(chuàng)造概念的一年。伴隨著Google一系列讓人耳目一新的交互式應用的發(fā)布,Jesse James Garrett的一篇文章《Ajax: A New Approach to Web Applications》被廣為傳播。Ajax這一前端特有的概念迅速將眾多分散的實踐統(tǒng)一在同一口號之下,引發(fā)了Web編程范式的轉換。所謂名不正則言不順,這下無名群眾可找到組織了。在未有Ajax之前,人們早已認識到了B/S架構的本質特征在于瀏覽器和服務器的狀態(tài)空間是分離的,但是一般的解決方案都是隱藏這一區(qū)分,將前臺狀態(tài)同步到后臺,由后臺統(tǒng)一進行邏輯處理,例如ASP.NET。因為缺乏成熟的設計模式支持前臺狀態(tài)駐留,在換頁的時候,已經裝載的js對象將被迫被丟棄,這樣誰還能指望它去完成什么復雜的工作嗎?
Ajax明確提出界面是局部刷新的,前臺駐留了狀態(tài),這就促成了一種需要:需要js對象在前臺存在更長的時間。這也就意味著需要將這些對象和功能有效的管理起來,意味著更復雜的代碼組織技術,意味著對模塊化,對公共代碼基的渴求。
jQuery現(xiàn)有的代碼中真正與Ajax相關(使用XMLHTTP控件異步訪問后臺返回數(shù)據(jù))的部分其實很少,但是如果沒有Ajax, jQuery作為公共代碼基也就缺乏存在的理由。
2. 模塊化:管理名字空間
當大量的代碼產生出來以后,我們所需要的最基礎的概念就是模塊化,也就是對工作進行分解和復用。工作得以分解的關鍵在于各人獨立工作的成果可以集成在一起。這意味著各個模塊必須基于一致的底層概念,可以實現(xiàn)交互,也就是說應該基于一套公共代碼基,屏蔽底層瀏覽器的不一致性,并實現(xiàn)統(tǒng)一的抽象層,例如統(tǒng)一的事件管理機制等。比統(tǒng)一代碼基更重要的是,各個模塊之間必須沒有名字沖突。否則,即使兩個模塊之間沒有任何交互,也無法共同工作。
jQuery目前鼓吹的主要賣點之一就是對名字空間的良好控制。這甚至比提供更多更完善的功能點都重要的多。良好的模塊化允許我們復用任何來源的代碼,所有人的工作得以積累疊加。而功能實現(xiàn)僅僅是一時的工作量的問題。jQuery使用module pattern的一個變種來減少對全局名字空間的影響,僅僅在window對象上增加了一個jQuery對象(也就是$函數(shù))。
所謂的module pattern代碼如下,它的關鍵是利用匿名函數(shù)限制臨時變量的作用域。
// 私有變量和函數(shù)
var privateThing = 'secret',
publicThing = 'not secret',
changePrivateThing = function() {
privateThing = 'super secret';
},
sayPrivateThing = function() {
console.log(privateThing);
changePrivateThing();
};
// 返回對外公開的API
return {
publicThing : publicThing,
sayPrivateThing : sayPrivateThing
}
})();
js本身缺乏包結構,不過經過多年的嘗試之后業(yè)內已經逐漸統(tǒng)一了對包加載的認識,形成了RequireJs庫這樣得到一定共識的解決方案。jQuery可以與RequireJS庫良好的集成在一起, 實現(xiàn)更完善的模塊依賴管理。http://requirejs.org/docs/jquery.html
//當jquery.js和jquery.my.js都成功裝載之后執(zhí)行
$(function(){
$('#my').myFunc();
});
});
通過以下函數(shù)調用來定義模塊my/shirt, 它依賴于my/cart和my/inventory模塊,
["my/cart", "my/inventory"],
function(cart, inventory) {
// 這里使用module pattern來返回my/shirt模塊對外暴露的API
return {
color: "blue",
size: "large"
addToCart: function() {
// decrement是my/inventory對外暴露的API
inventory.decrement(this);
cart.add(this);
}
}
}
);
3. 神奇的$:對象提升
當你第一眼看到$函數(shù)的時候,你想到了什么?傳統(tǒng)的編程理論總是告訴我們函數(shù)命名應該準確,應該清晰無誤的表達作者的意圖,甚至聲稱長名字要優(yōu)于短名字,因為減少了出現(xiàn)歧義的可能性。但是,$是什么?亂碼?它所傳遞的信息實在是太隱晦,太曖昧了。$是由prototype.js庫發(fā)明的,它真的是一個神奇的函數(shù),因為它可以將一個原始的DOM節(jié)點提升(enhance)為一個具有復雜行為的對象。在prototype.js最初的實現(xiàn)中,$函數(shù)的定義為
return "string" == typeof id ? document.getElementById(id) : id;
};
這基本對應于如下公式
e = $(id)
這絕不僅僅是提供了一個聰明的函數(shù)名稱縮寫,更重要的是在概念層面上建立了文本id與DOM element之間的一一對應。在未有$之前,id與對應的element之間的距離十分遙遠,一般要將element緩存到變量中,例如
var eb = docuement.getElementById('b');
ea.style....
但是使用$之后,卻隨處可見如下的寫法
$('body_'+id)....
id與element之間的距離似乎被消除了,可以非常緊密的交織在一起。
prototype.js后來擴展了$的含義,
var elements = new Array();
for (var i = 0; i < arguments.length; i++) {
var element = arguments[i];
if (typeof element == 'string')
element = document.getElementById(element);
if (arguments.length == 1)
return element;
elements.push(element);
}
return elements;
}
這對應于公式:
[e,e] = $(id,id)
很遺憾,這一步prototype.js走偏了,這一做法很少有實用的價值。
真正將$發(fā)揚光大的是jQuery, 它的$對應于公式
[o] = $(selector)
這里有三個增強:
A. selector不再是單一的節(jié)點定位符,而是復雜的集合選擇符
B. 返回的元素不是原始的DOM節(jié)點,而是經過jQuery進一步增強的具有豐富行為的對象,可以啟動復雜的函數(shù)調用鏈。
C. $返回的包裝對象被造型為數(shù)組形式,將集合操作自然的整合到調用鏈中。
當然,以上僅僅是對神奇的$的一個過分簡化的描述,它的實際功能要復雜得多. 特別是有一個非常常用的直接構造功能.
jQuery將根據(jù)傳入的html文本直接構造出一系列的DOM節(jié)點,并將其包裝為jQuery對象. 這在某種程度上可以看作是對selector的擴展: html內容描述本身就是一種唯一指定.
$(function{})這一功能就實在是讓人有些無語了, 它表示當document.ready的時候調用此回調函數(shù)。真的,$是一個神奇的函數(shù), 有任何問題,請$一下。
總結起來, $是從普通的DOM和文本描述世界到具有豐富對象行為的jQuery世界的躍遷通道??邕^了這道門,就來到了理想國。
4. 無定形的參數(shù):專注表達而不是約束
弱類型語言既然頭上頂著個"弱"字, 總難免讓人有些先天不足的感覺. 在程序中缺乏類型約束, 是否真的是一種重大的缺憾? 在傳統(tǒng)的強類型語言中, 函數(shù)參數(shù)的類型,個數(shù)等都是由編譯器負責檢查的約束條件, 但這些約束仍然是遠遠不夠的. 一般應用程序中為了加強約束, 總會增加大量防御性代碼, 例如在C++中我們常用ASSERT, 而在java中也經常需要判斷參數(shù)值的范圍
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
很顯然, 這些代碼將導致程序中存在大量無功能的執(zhí)行路徑, 即我們做了大量判斷, 代碼執(zhí)行到某個點, 系統(tǒng)拋出異常, 大喊此路不通. 如果我們換一個思路, 既然已經做了某種判斷,能否利用這些判斷的結果來做些什么呢? javascript是一種弱類型的語言,它是無法自動約束參數(shù)類型的, 那如果順勢而行,進一步弱化參數(shù)的形態(tài), 將"弱"推進到一種極致, 在弱無可弱的時候, weak會不會成為標志性的特點?
看一下jQuery中的事件綁定函數(shù)bind,
A. 一次綁定一個事件
B. 一次綁定多個事件
C. 換一個形式, 同樣綁定多個事件
D. 想給事件監(jiān)聽器傳點參數(shù)
E. 想給事件監(jiān)聽器分個組
F. 這個函數(shù)為什么還沒有瘋掉???
就算是類型不確定, 在固定位置上的參數(shù)的意義總要是確定的吧? 退一萬步來說, 就算是參數(shù)位置不重要了,函數(shù)本身的意義應該是確定的吧? 但這是什么?
取值 value = o.val(), 設置值 o.val(3)
一個函數(shù)怎么可以這樣過分, 怎么能根據(jù)傳入?yún)?shù)的類型和個數(shù)不同而行為不同呢? 看不順眼是不是? 可這就是俺們的價值觀. 既然不能防止, 那就故意允許. 雖然形式多變, 卻無一句廢話. 缺少約束, 不妨礙表達(我不是出來嚇人的).
5. 鏈式操作: 線性化的逐步細化
jQuery早期最主要的賣點就是所謂的鏈式操作(chain).
.find('h3') // 選擇所有后代h3節(jié)點
.eq(2) // 過濾集合, 保留第三個元素
.html('改變第三個h3的文本')
.end() // 返回上一級的h3集合
.eq(0)
.html('改變第一個h3的文本');
在一般的命令式語言中, 我們總需要在重重嵌套循環(huán)中過濾數(shù)據(jù), 實際操作數(shù)據(jù)的代碼與定位數(shù)據(jù)的代碼糾纏在一起. 而jQuery采用先構造集合然后再應用函數(shù)于集合的方式實現(xiàn)兩種邏輯的解耦, 實現(xiàn)嵌套結構的線性化. 實際上, 我們并不需要借助過程化的思想就可以很直觀的理解一個集合, 例如 $('div.my input:checked')可以看作是一種直接的描述,而不是對過程行為的跟蹤.
循環(huán)意味著我們的思維處于一種反復回繞的狀態(tài), 而線性化之后則沿著一個方向直線前進, 極大減輕了思維負擔, 提高了代碼的可組合性. 為了減少調用鏈的中斷, jQuery發(fā)明了一個絕妙的主意: jQuery包裝對象本身類似數(shù)組(集合). 集合可以映射到新的集合, 集合可以限制到自己的子集合,調用的發(fā)起者是集合,返回結果也是集合,集合可以發(fā)生結構上的某種變化但它還是集合, 集合是某種概念上的不動點,這是從函數(shù)式語言中吸取的設計思想。集合操作是太常見的操作, 在java中我們很容易發(fā)現(xiàn)大量所謂的封裝函數(shù)其實就是在封裝一些集合遍歷操作, 而在jQuery中集合操作因為太直白而不需要封裝.
鏈式調用意味著我們始終擁有一個“當前”對象,所有的操作都是針對這一當前對象進行。這對應于如下公式
x += dx
調用鏈的每一步都是對當前對象的增量描述,是針對最終目標的逐步細化過程。Witrix平臺中對這一思想也有著廣泛的應用。特別是為了實現(xiàn)平臺機制與業(yè)務代碼的融合,平臺會提供對象(容器)的缺省內容,而業(yè)務代碼可以在此基礎上進行逐步細化的修正,包括取消缺省的設置等。
話說回來, 雖然表面上jQuery的鏈式調用很簡單, 內部實現(xiàn)的時候卻必須自己多寫一層循環(huán), 因為編譯器并不知道"自動應用于集合中每個元素"這回事.
return this.each(function(){
jQuery.someFunc(this,...);
}
}
6. data: 統(tǒng)一數(shù)據(jù)管理
作為一個js庫,它必須解決的一個大問題就是js對象與DOM節(jié)點之間的狀態(tài)關聯(lián)與協(xié)同管理問題。有些js庫選擇以js對象為主,在js對象的成員變量中保存DOM節(jié)點指針,訪問時總是以js對象為入口點,通過js函數(shù)間接操作DOM對象。在這種封裝下,DOM節(jié)點其實只是作為界面展現(xiàn)的一種底層“匯編”而已。jQuery的選擇與Witrix平臺類似,都是以HTML自身結構為基礎,通過js增強(enhance)DOM節(jié)點的功能,將它提升為一個具有復雜行為的擴展對象。這里的思想是非侵入式設計(non-intrusive)和優(yōu)雅退化機制(graceful degradation)。語義結構在基礎的HTML層面是完整的,js的作用是增強了交互行為,控制了展現(xiàn)形式。
如果每次我們都通過$('#my')的方式來訪問相應的包裝對象,那么一些需要長期保持的狀態(tài)變量保存在什么地方呢?jQuery提供了一個統(tǒng)一的全局數(shù)據(jù)管理機制。
獲取數(shù)據(jù):
設置數(shù)據(jù):
這一機制自然融合了對HTML5的data屬性的處理
通過 $('#my').data('myAttr')將可以讀取到HTML中設置的數(shù)據(jù)。
第一次訪問data時,jQuery將為DOM節(jié)點分配一個唯一的uuid, 然后設置在DOM節(jié)點的一個特定的expando屬性上, jQuery保證這個uuid在本頁面中不重復。
以上代碼可以同時處理DOM節(jié)點和純js對象的情況。如果是js對象,則data直接放置在js對象自身中,而如果是DOM節(jié)點,則通過cache統(tǒng)一管理。
因為所有的數(shù)據(jù)都是通過data機制統(tǒng)一管理的,特別是包括所有事件監(jiān)聽函數(shù)(data.events),因此jQuery可以安全的實現(xiàn)資源管理。在clone節(jié)點的時候,可以自動clone其相關的事件監(jiān)聽函數(shù)。而當DOM節(jié)點的內容被替換或者DOM節(jié)點被銷毀的時候,jQuery也可以自動解除事件監(jiān)聽函數(shù), 并安全的釋放相關的js數(shù)據(jù)。
7. event:統(tǒng)一事件模型
"事件沿著對象樹傳播"這一圖景是面向對象界面編程模型的精髓所在。對象的復合構成對界面結構的一個穩(wěn)定的描述,事件不斷在對象樹的某個節(jié)點發(fā)生,并通過冒泡機制向上傳播。對象樹很自然的成為一個控制結構,我們可以在父節(jié)點上監(jiān)聽所有子節(jié)點上的事件,而不用明確與每一個子節(jié)點建立關聯(lián)。
jQuery除了為不同瀏覽器的事件模型建立了統(tǒng)一抽象之外,主要做了如下增強:
A. 增加了自定制事件(custom)機制. 事件的傳播機制與事件內容本身原則上是無關的, 因此自定制事件完全可以和瀏覽器內置事件通過同一條處理路徑, 采用同樣的監(jiān)聽方式. 使用自定制事件可以增強代碼的內聚性, 減少代碼耦合. 例如如果沒有自定制事件, 關聯(lián)代碼往往需要直接操作相關的對象
var $light = $(this).parent().find('.lightbulb');
if ($light.hasClass('on')) {
$light.removeClass('on').addClass('off');
} else {
$light.removeClass('off').addClass('on');
}
});
而如果使用自定制事件,則表達的語義更加內斂明確,
$(this).parent().find('.lightbulb').trigger('changeState');
});
B. 增加了對動態(tài)創(chuàng)建節(jié)點的事件監(jiān)聽. bind函數(shù)只能將監(jiān)聽函數(shù)注冊到已經存在的DOM節(jié)點上. 例如
如果調用bind之后,新建了另一個li節(jié)點,則該節(jié)點的click事件不會被監(jiān)聽.
jQuery的delegate機制可以將監(jiān)聽函數(shù)注冊到父節(jié)點上, 子節(jié)點上觸發(fā)的事件會根據(jù)selector被自動派發(fā)到相應的handlerFn上. 這樣一來現(xiàn)在注冊就可以監(jiān)聽未來創(chuàng)建的節(jié)點.
最近jQuery1.7中統(tǒng)一了bind, live和delegate機制, 天下一統(tǒng), 只有on/off.
$('#myList').on('click', 'li.trigger', handlerFn); // 相當于delegate
8. 動畫隊列:全局時鐘協(xié)調
拋開jQuery的實現(xiàn)不談, 先考慮一下如果我們要實現(xiàn)界面上的動畫效果, 到底需要做些什么? 比如我們希望將一個div的寬度在1秒鐘之內從100px增加到200px. 很容易想見, 在一段時間內我們需要不時的去調整一下div的寬度, [同時]我們還需要執(zhí)行其他代碼. 與一般的函數(shù)調用不同的是, 發(fā)出動畫指令之后, 我們不能期待立刻得到想要的結果, 而且我們不能原地等待結果的到來. 動畫的復雜性就在于:一次性表達之后要在一段時間內執(zhí)行,而且有多條邏輯上的執(zhí)行路徑要同時展開, 如何協(xié)調?
偉大的艾薩克.牛頓爵士在《自然哲學的數(shù)學原理》中寫道:"絕對的、真正的和數(shù)學的時間自身在流逝著". 所有的事件可以在時間軸上對齊, 這就是它們內在的協(xié)調性. 因此為了從步驟A1執(zhí)行到A5, 同時將步驟B1執(zhí)行到B5, 我們只需要在t1時刻執(zhí)行[A1, B1], 在t2時刻執(zhí)行[A2,B2], 依此類推.
t1 | t2 | t3 | t4 | t5 ...
A1 | A2 | A3 | A4 | A5 ...
B1 | B2 | B3 | B4 | B5 ...
具體的一種實現(xiàn)形式可以是
A. 對每個動畫, 將其分裝為一個Animation對象, 內部分成多個步驟.
animation = new Animation(div,"width",100,200,1000,
負責步驟切分的插值函數(shù),動畫執(zhí)行完畢時的回調函數(shù));
B. 在全局管理器中注冊動畫對象
timerFuncs.add(animation);
C. 在全局時鐘的每一個觸發(fā)時刻, 將每個注冊的執(zhí)行序列推進一步, 如果已經結束, 則從全局管理器中刪除.
if(!animation.doOneStep())
timerFuncs.remove(animation)
解決了原理問題,再來看看表達問題, 怎樣設計接口函數(shù)才能夠以最緊湊形式表達我們的意圖? 我們經常需要面臨的實際問題:
A. 有多個元素要執(zhí)行類似的動畫
B. 每個元素有多個屬性要同時變化
C. 執(zhí)行完一個動畫之后開始另一個動畫
jQuery對這些問題的解答可以說是榨盡了js語法表達力的最后一點剩余價值.
.animate({left:'+=200px',top:'300'},2000)
.animate({left:'-=200px',top:20},1000)
.queue(function(){
// 這里dequeue將首先執(zhí)行隊列中的后一個函數(shù),因此alert("y")
$(this).dequeue();
alert('x');
})
.queue(function(){
alert("y");
// 如果不主動dequeue, 隊列執(zhí)行就中斷了,不會自動繼續(xù)下去.
$(this).dequeue();
});
A. 利用jQuery內置的selector機制自然表達對一個集合的處理.
B. 使用Map表達多個屬性變化
C. 利用微格式表達領域特定的差量概念. '+=200px'表示在現(xiàn)有值的基礎上增加200px
D. 利用函數(shù)調用的順序自動定義animation執(zhí)行的順序: 在后面追加到執(zhí)行隊列中的動畫自然要等前面的動畫完全執(zhí)行完畢之后再啟動.
jQuery動畫隊列的實現(xiàn)細節(jié)大概如下所示,
A. animate函數(shù)實際是調用queue(function(){執(zhí)行結束時需要調用dequeue,否則不會驅動下一個方法})
queue函數(shù)執(zhí)行時, 如果是fx隊列, 并且當前沒有正在運行動畫(如果連續(xù)調用兩次animate,第二次的執(zhí)行函數(shù)將在隊列中等待),則會自動觸發(fā)dequeue操作, 驅動隊列運行.
如果是fx隊列, dequeue的時候會自動在隊列頂端加入"inprogress"字符串,表示將要執(zhí)行的是動畫.
B. 針對每一個屬性,創(chuàng)建一個jQuery.fx對象。然后調用fx.custom函數(shù)(相當于start)來啟動動畫。
C. custom函數(shù)中將fx.step函數(shù)注冊到全局的timerFuncs中,然后試圖啟動一個全局的timer.
timerId = setInterval( fx.tick, fx.interval );
D. 靜態(tài)的tick函數(shù)中將依次調用各個fx的step函數(shù)。step函數(shù)中通過easing計算屬性的當前值,然后調用fx的update來更新屬性。
E. fx的step函數(shù)中判斷如果所有屬性變化都已完成,則調用dequeue來驅動下一個方法。
很有意思的是, jQuery的實現(xiàn)代碼中明顯有很多是接力觸發(fā)代碼: 如果需要執(zhí)行下一個動畫就取出執(zhí)行, 如果需要啟動timer就啟動timer等. 這是因為js程序是單線程的,真正的執(zhí)行路徑只有一條,為了保證執(zhí)行線索不中斷, 函數(shù)們不得不互相幫助一下. 可以想見, 如果程序內部具有多個執(zhí)行引擎, 甚至無限多的執(zhí)行引擎, 那么程序的面貌就會發(fā)生本質性的改變. 而在這種情形下, 遞歸相對于循環(huán)而言會成為更自然的描述.
9. promise模式:因果關系的識別
現(xiàn)實中,總有那么多時間線在獨立的演化著, 人與物在時空中交錯,卻沒有發(fā)生因果. 軟件中, 函數(shù)們在源代碼中排著隊, 難免會產生一些疑問, 憑什么排在前面的要先執(zhí)行? 難道沒有它就沒有我? 讓全宇宙喊著1,2,3齊步前進, 從上帝的角度看,大概是管理難度過大了, 于是便有了相對論. 如果相互之間沒有交換信息, 沒有產生相互依賴, 那么在某個坐標系中順序發(fā)生的事件, 在另外一個坐標系中看來, 就可能是顛倒順序的. 程序員依葫蘆畫瓢, 便發(fā)明了promise模式.
promise與future模式基本上是一回事,我們先來看一下java中熟悉的future模式.
...
realResult = futureResult.get();
發(fā)出函數(shù)調用僅僅意味著一件事情發(fā)生過, 并不必然意味著調用者需要了解事情最終的結果. 函數(shù)立刻返回的只是一個將在未來兌現(xiàn)的承諾(Future類型), 實際上也就是某種句柄. 句柄被傳來傳去, 中間轉手的代碼對實際結果是什么,是否已經返回漠不關心. 直到一段代碼需要依賴調用返回的結果, 因此它打開future, 查看了一下. 如果實際結果已經返回, 則future.get()立刻返回實際結果, 否則將會阻塞當前的執(zhí)行路徑, 直到結果返回為止. 此后再調用future.get()總是立刻返回, 因為因果關系已經被建立, [結果返回]這一事件必然在此之前發(fā)生, 不會再發(fā)生變化.
future模式一般是外部對象主動查看future的返回值, 而promise模式則是由外部對象在promise上注冊回調函數(shù).
return $.get('/foo/').done(function(){
console.log('Fires after the AJAX request succeeds');
}).fail(function(){
console.log('Fires after the AJAX request fails');
});
}
function showDiv(){
var dfd = $.Deferred();
$('#foo').fadeIn( 1000, dfd.resolve );
return dfd.promise();
}
$.when( getData(), showDiv() )
.then(function( ajaxResult, ignoreResultFromShowDiv ){
console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');
// 'ajaxResult' is the server's response
});
jQuery引入Deferred結構, 根據(jù)promise模式對ajax, queue, document.ready等進行了重構, 統(tǒng)一了異步執(zhí)行機制. then(onDone, onFail)將向promise中追加回調函數(shù), 如果調用成功完成(resolve), 則回調函數(shù)onDone將被執(zhí)行, 而如果調用失敗(reject), 則onFail將被執(zhí)行. when可以等待在多個promise對象上. promise巧妙的地方是異步執(zhí)行已經開始之后甚至已經結束之后,仍然可以注冊回調函數(shù)
someObj.done(callback).sendRequest() vs. someObj.sendRequest().done(callback)
callback函數(shù)在發(fā)出異步調用之前注冊或者在發(fā)出異步調用之后注冊是完全等價的, 這揭示出程序表達永遠不是完全精確的, 總存在著內在的變化維度. 如果能有效利用這一內在的可變性, 則可以極大提升并發(fā)程序的性能.
promise模式的具體實現(xiàn)很簡單. jQuery._Deferred定義了一個函數(shù)隊列,它的作用有以下幾點:
A. 保存回調函數(shù)。
B. 在resolve或者reject的時刻把保存著的函數(shù)全部執(zhí)行掉。
C. 已經執(zhí)行之后, 再增加的函數(shù)會被立刻執(zhí)行。
一些專門面向分布式計算或者并行計算的語言會在語言級別內置promise模式, 比如E語言.
def temperaturePromise := carPromise <- getEngineTemperature()
...
when (temperaturePromise) -> done(temperature) {
println(`The temperature of the car engine is: $temperature`)
} catch e {
println(`Could not get engine temperature, error: $e`)
}
在E語言中, <-是eventually運算符, 表示最終會執(zhí)行, 但不一定是現(xiàn)在. 而普通的car.moveTo(2,3)表示立刻執(zhí)行得到結果. 編譯器負責識別所有的promise依賴, 并自動實現(xiàn)調度.
10. extend: 繼承不是必須的
js是基于原型的語言, 并沒有內置的繼承機制, 這一直讓很多深受傳統(tǒng)面向對象教育的同學們耿耿于懷. 但繼承一定是必須的嗎? 它到底能夠給我們帶來什么? 最純樸的回答是: 代碼重用. 那么, 我們首先來分析一下繼承作為代碼重用手段的潛力.
曾經有個概念叫做"多重繼承", 它是繼承概念的超級賽亞人版, 很遺憾后來被診斷為存在著先天缺陷, 以致于出現(xiàn)了一種對于繼承概念的解讀: 繼承就是"is a"關系, 一個派生對象"is a"很多基類, 必然會出現(xiàn)精神分裂, 所以多重繼承是不好的.
class B{ public: void f(){ f in B } }
class D: public A, B{}
如果D類從A,B兩個基類繼承, 而A和B類中都實現(xiàn)了同一個函數(shù)f, 那么D類中的f到底是A中的f還是B中的f, 抑或是A中的f+B中的f呢? 這一困境的出現(xiàn)實際上源于D的基類A和B是并列關系, 它們滿足交換律和結合律, 畢竟,在概念層面上我們可能難以認可兩個任意概念之間會出現(xiàn)從屬關系. 但如果我們放松一些概念層面的要求, 更多的從操作層面考慮一下代碼重用問題, 可以簡單的認為B在A的基礎上進行操作, 那么就可以得到一個線性化的結果. 也就是說, 放棄A和B之間的交換律只保留結合律, extends A, B 與 extends B,A 會是兩個不同的結果, 不再存在詮釋上的二義性. scala語言中的所謂trait(特性)機制實際上采用的就是這一策略.
面向對象技術發(fā)明很久之后, 出現(xiàn)了所謂的面向方面編程(AOP), 它與OOP不同, 是代碼結構空間中的定位與修改技術. AOP的眼中只有類與方法, 不知道什么叫做意義. AOP也提供了一種類似多重繼承的代碼重用手段, 那就是mixin. 對象被看作是可以被打開,然后任意修改的Map, 一組成員變量與方法就被直接注射到對象體內, 直接改變了它的行為.
prototype.js庫引入了extend函數(shù),
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
就是Map之間的一個覆蓋運算, 但很管用, 在jQuery庫中也得到了延用. 這個操作類似于mixin, 在jQuery中是代碼重用的主要技術手段---沒有繼承也沒什么大不了的.
11. 名稱映射: 一切都是數(shù)據(jù)
代碼好不好, 循環(huán)判斷必須少. 循環(huán)和判斷語句是程序的基本組成部分, 但是優(yōu)良的代碼庫中卻往往找不到它們的蹤影, 因為這些語句的交織會模糊系統(tǒng)的邏輯主線, 使我們的思想迷失在疲于奔命的代碼追蹤中. jQuery本身通過each, extend等函數(shù)已經極大減少了對循環(huán)語句的需求, 對于判斷語句, 則主要是通過映射表來處理. 例如, jQuery的val()函數(shù)需要針對不同標簽進行不同的處理, 因此定義一個以tagName為key的函數(shù)映射表
這樣在程序中就不需要到處寫
return ...;
}else if(elm.tagName == 'TEXTAREA'){
return ...;
}
可以統(tǒng)一處理
映射表將函數(shù)作為普通數(shù)據(jù)來管理, 在動態(tài)語言中有著廣泛的應用. 特別是, 對象本身就是函數(shù)和變量的容器, 可以被看作是映射表. jQuery中大量使用的一個技巧就是利用名稱映射來動態(tài)生成代碼, 形成一種類似模板的機制. 例如為了實現(xiàn)myWidth和myHeight兩個非常類似的函數(shù), 我們不需要
return parseInt(this.style.width,10) + 10;
}
jQuery.fn.myHeight = function(){
return parseInt(this.style.height,10) + 10;
}
而可以選擇動態(tài)生成
jQuery.fn['my'+name] = function(){
return parseInt(this.style[name.toLowerCase()],10) + 10;
}
});
12. 插件機制:其實我很簡單
jQuery所謂的插件其實就是$.fn上增加的函數(shù), 那這個fn是什么東西?
// 內部又有一個包裝
var jQuery = (function() {
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
}
....
// fn實際就是prototype的簡寫
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {... }
}
// 調用jQuery()就是相當于new init(), 而init的prototype就是jQuery的prototype
jQuery.fn.init.prototype = jQuery.fn;
// 這里返回的jQuery對象只具備最基本的功能, 下面就是一系列的extend
return jQuery;
})();
...
// 將jQuery暴露為全局對象
window.jQuery = window.$ = jQuery;
})(window);
顯然, $.fn其實就是jQuery.prototype的簡寫.
無狀態(tài)的插件僅僅就是一個函數(shù), 非常簡單.
(function($){
$.fn.hoverClass = function(c) {
return this.hover(
function() { $(this).toggleClass(c); }
);
};
})(jQuery);
// 使用插件
$('li').hoverClass('hover');
對于比較復雜的插件開發(fā), jQuery UI提供了一個widget工廠機制,
options: {
autoOpen: true,...
},
_create: function(){ ... },
_init: function() {
if ( this.options.autoOpen ) {
this.open();
}
},
_setOption: function(key, value){ ... }
destroy: function(){ ... }
});
調用 $('#dlg').dialog(options)時, 實際執(zhí)行的代碼基本如下所示:
var instance = $.data( this, "dialog" );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, "dialog", new $.ui.dialog( options, this ) );
}
}
可以看出, 第一次調用$('#dlg').dialog()函數(shù)時會創(chuàng)建窗口對象實例,并保存在data中, 此時會調用_create()和_init()函數(shù), 而如果不是第一次調用, 則是在已經存在的對象實例上調用_init()方法. 多次調用$('#dlg').dialog()并不會創(chuàng)建多個實例.
13. browser sniffer vs. feature detection
瀏覽器嗅探(browser sniffer)曾經是很流行的技術, 比如早期的jQuery中
version:(userAgent.match(/.+(?:rv|it|ra|ie)[/: ]([d.]+)/) || [0,'0'])[1],
safari:/webkit/.test(userAgent),
opera:/opera/.test(userAgent),
msie:/msie/.test(userAgent) && !/opera/.test(userAgent),
mozilla:/mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
};
在具體代碼中可以針對不同的瀏覽器作出不同的處理
// do something
} else if($.browser.opera) {
// ...
}
但是隨著瀏覽器市場的競爭升級, 競爭對手之間的互相模仿和偽裝導致userAgent一片混亂, 加上Chrome的誕生, Safari的崛起, IE也開始加速向標準靠攏, sniffer已經起不到積極的作用. 特性檢測(feature detection)作為更細粒度, 更具體的檢測手段, 逐漸成為處理瀏覽器兼容性的主流方式.
// IE strips leading whitespace when .innerHTML is used
leadingWhitespace: ( div.firstChild.nodeType === 3 ),
...
}
只基于實際看見的,而不是曾經知道的, 這樣更容易做到兼容未來.
14. Prototype vs. jQuery
prototype.js是一個立意高遠的庫, 它的目標是提供一種新的使用體驗,參照Ruby從語言級別對javascript進行改造,并最終真的極大改變了js的面貌。$, extends, each, bind...這些耳熟能詳?shù)母拍疃际莗rototype.js引入到js領域的. 它肆無忌憚的在window全局名字空間中增加各種概念, 大有誰先占坑誰有理, 舍我其誰的氣勢. 而jQuery則扣扣索索, 抱著比較實用化的理念, 目標僅僅是write less, do more而已.
不過等待激進的理想主義者的命運往往都是壯志未酬身先死. 當prototype.js標志性的bind函數(shù)等被吸收到ECMAScript標準中時, 便注定了它的沒落. 到處修改原生對象的prototype, 這是prototype.js的獨門秘技, 也是它的死穴. 特別是當它試圖模仿jQuery, 通過Element.extend(element)返回增強對象的時候, 算是徹底被jQuery給帶到溝里去了. prototype.js與jQuery不同, 它總是直接修改原生對象的prototype, 而瀏覽器卻是充滿bug, 謊言, 歷史包袱并夾雜著商業(yè)陰謀的領域, 在原生對象層面解決問題注定是一場悲劇. 性能問題, 名字沖突, 兼容性問題等等都是一個幫助庫的能力所無法解決的. Prototype.js的2.0版本據(jù)說要做大的變革, 不知是要與歷史決裂, 放棄兼容性, 還是繼續(xù)掙扎, 在夾縫中求生.
希望本文所述對大家的jQuery程序設計有所幫助。
相關文章
ASP.NET jQuery 實例6 (實現(xiàn)CheckBoxList成員全選或全取消)
ASP.NET jQuery 實例6 (實現(xiàn)CheckBoxList成員全選或全取消) ,需要的朋友可以參考下。2012-01-01
jQuery實現(xiàn)的動態(tài)伸縮導航菜單實例
這篇文章主要介紹了jQuery實現(xiàn)的動態(tài)伸縮導航菜單,實例分析了jQuery鼠標事件及animate、hide等方法的使用技巧,需要的朋友可以參考下2015-05-05
jQuery實現(xiàn)鼠標拖動div改變位置、大小的實踐
這篇文章主要介紹了jQuery實現(xiàn)鼠標拖動div改變位置、大小的實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04

