說明:14年研究jquery源碼的總結node
本文對jquery1.7版本進行了閱讀學習,將整個jquery源碼拆分爲11個模塊,這些模塊相互依賴,構成了一個簡單、強大的js類庫。jquery是一個基於DOM操做的類庫,所以Sizzle選擇器引擎的實現就顯得尤其重要。針對Sizzle選擇器引擎的實現,以前已經作過先關的分析,參見:sizzle選擇器引擎介紹 。下面對其中的數據存儲、事件處理、異常請求ajax、動畫等進行簡單的介紹。jquery
(function(window, undefined) { var jQuery = function(selector, context) { return new jQuery.fn.init(selector, context, rootJquery); } jQuery.fn = function() { // 原型對象 ... } // 工具方法 // 異步隊列 // 隊列queue // 瀏覽器測試support // 屬性操做 // 事件系統 // DOM遍歷與操做、樣式操做 // ajax請求 // 動畫 })(window)
在實際的項目開發中,常常須要把某些信息附加到一個DOM節點中,那麼若是管理DOM節點和附加數據的關係,就顯得很是重要。很明顯,目前有兩種思路來解決這個問題:ajax
1)直接附加在DOM節點上編程
2)經過一個id來關聯DOM節點、附加數據。這兩種方法各有利弊:方法1的好處是DOM節點、附加數據在一塊兒,便於維護;方法2的好處是能夠避免相互依賴,從而避免內存泄露問題。json
var arr = []; function createNode() { return document.createElement('div'); } function saveNodes() { for(var i=0; i<100; i++) { arr.push(createNode()); } } // 將上述100個nodes節點渲染到頁面,以後在將其從頁面中剔除掉,那麼由於arr引用着100個nodes節點,這些節點就沒法被垃圾回收機制回收,從而引發內存泄露問題。
jQuery中關於異步的實現,大體上遵循Promise/A規範,爲何說是大體呢?segmentfault
then的返回:then方法並無返回新的異步對象,這不符合Promise/A規範中的then要求。這一點在jQuery2.0之後已經被修改跨域
結果處理:在進行結果處理的時候,jQuery並無進行結果的異常捕獲數組
參數的個數:jquery中resolve能夠有多個參數,而Promise/A的resolve僅能有一個瀏覽器
內部實現比較曲折抽象,代碼晦澀難懂,主要是經過"once"、"memory"兩個參數進行控制:"once"決定異步的回調函數只能被執行一次;「memory」決定函數具備記憶動能,也就是異步事件完成之後,再綁定回調函數,回調函數會當即執行。緩存
jquery內部事件的功能很強大,除了能夠處理DOM事件外,還能夠自定義事件、觸發事件(DOM事件、自定義事件)、定義事件的命名空間等強大功能。而且jquery內部的另一大亮點就是經過數據存儲模塊(.data),儘可能下降DOM和監聽事件之間的依賴,避免DOM、js對象相互依賴形成的內存泄露。在數據緩存模塊的數據結構以下:
$('.a').on('click', function() {}); // results is as fellow: $.cache = { 1: { events: { click: [ //click.delegateCount: 記錄代理事件的個數,代理回調函數放在數組的前面 { data: ..., guid: ..., selector: ..., handler: function() {}, // handler.guid = 1 用於定位和移除監聽函數 .... } ] }, handle: function() {......} // 主監聽函數 } }
當綁定事件時,內部方法的調用鏈爲:bind/delegate/live/one()--->.on()—>$.event.add()—>$.data()/addEventListener/attachEvent()。其實在對於一個DOM元素,全部的事件都對應一個主監聽函數($._data(elem).handle),而後經過主監聽函數經過事件分發函數($.event.dispatch)來觸發相應類型的監聽函數。
$('.a').on('click', fn1); $('.a').on('blur', fn2); $('.a').on('focus', fn3); // 其實DOM元素(.a)並無直接與fn一、fn二、fn3關聯起來,而是經過dispatch進行事件分發 // 用僞代碼能夠表示以下: $('.a').on('click blur focus', dispatch); function dispatch(type) { var fn; if(type == 'click') { fn = fn1; } else if(type= == 'blur') { fn = fn2; } else if (type == 'focus') { fn = fn3; } return fn; }
當移除事件時,內部方法的調用鏈爲:unbind/delagate/die()--->.off()—>$.event.remove()—>$._data()/removeEventListener/detachEvent()。事件的移除,也就是從數據存儲對象$.cache中移除相應的事件對象,當事件對象events爲空時,則移除整個數據緩存對象。
在jquery中,能夠手工觸發DOM事件或自定義事件。內部方法的調用鏈爲:.trigger/triggerHandler() —> $.event.trigger() — > $.event.dispatch(主監聽函數) — >事件的監聽函數。
還有一個須要關注的問題就是,如何事件事件的冒泡呢?方法其實很簡單,就是根據DOM的結構向上查詢出元素的祖先元素,一直到window對象,這樣就構成了元素的冒泡路徑,而後觸發這個路徑上元素的相應事件。這也是jquery能夠模擬focus、blur、change、submit進行事件冒泡的關鍵環節。
// 尋找冒泡路徑 var eventPath = []; for(;cur; cur = cur.parentNode) { eventPath.push([cur, type]); } // 執行路徑上的監聽函數 for() { cur = eventPath[i][0]; type = eventPath[i][4]; hanle = $._data(cur, "events")[type]; hanle.apply(cur); }
異步請求是jquery在整體能夠分爲三部分:核心實現、便捷方法、ajax全局事件。其中該模塊依賴於Deferred模塊提供的異步編程模塊,能夠方便進行回調函數的註冊,例如:
$(url, options).then(successFn, failFn);
其中核心方法的實現主要包括如下驟:
參數的設置:在jQuery全部API中,.ajax的參數種類應該是對多的,裏面的參數看得人掩護繚亂。可是其中最終的有url、type、dataType、data,尤爲是dataType的設置對於結果的應該很長大,因此有大量代碼是對這一步的處理
前置過濾函數處理:主要是對json、jsonp、script三種數據類型的處理,在請求發送前對其進行過濾處理
請求發出:這裏請求的發出包括兩種方式,分別爲依賴於XMLHttpRequest和script標籤。若是瀏覽器容許跨域,則僅需使用XMLHttpRequest就足夠了
回調函數的執行:這裏的回調函數包括不少種,請求發出前、開始接受數據、數據接受完成等等
在jQuery中,動畫show、hide、fadeIn、fadeOut等均要調用Animation方法,也就是說Animation是最基本的入口函數。在上圖中能夠看到該入口函數包括了三個過程:參數配置、生成動畫函數、動畫函數執行。下面展開包括如下細節:
參數配置:主要有三個參數,duration表示動畫的執行時間;easing爲動畫每一幀的變化速度,目前jQuery中僅存在兩種幀變化函數:線性(linear)變化、餘弦變化(swing),要用其餘變化函數只能修改$.fx.easing對象;complete爲動畫完成以後須要執行的回調函數
生成動畫函數doAnimation:jQuery會給每個樣式生成一個$.fx對象,該對象用於實現動畫效果。默認狀況下,每隔13ms會執行一幀動畫,而後更新頁面的樣式
動畫執行:若是動畫不須要排隊,在生成完動畫函數以後,就當即執行動畫函數doAnimation;反之,將doAnimation放入隊列queue中進行排隊。當全部動畫均完成以後,就能夠執行回調函數complete了。
固然,jQuery內部實現比較複雜,考慮了函數暫停、樣式臨時修改(修改inline元素的width/height時,會臨時將其display修改成inline-block)、清空動畫隊列等操做。