參考資料
https://developer.chrome.com/devtools/docs/demos/memory/example6
目前爲止,jquery是js社區中最活躍、用戶最多的前端類庫,具備鏈式操做、兼容性、基於數組的操做、強大的插件機制等特色,也是不少前端入門同窗最先接觸到的庫。可是內部如何實現的,一直吸引着我。所以最近三個月讀完了jquery1.7版本的設計,之因此選擇該版本是由於Sizzle在1.8以後引入了編譯函數,代碼變更比較大。html
1.整體設計前端
本文對jquery1.7版本進行了閱讀學習,將整個jquery源碼拆分爲11個模塊,這些模塊相互依賴,構成了一個簡單、強大的js類庫。jquery是一個基於DOM操做的類庫,所以Sizzle選擇器引擎的實現就顯得尤其重要。針對Sizzle選擇器引擎的實現,以前已經作過先關的分析,參見:sizzle選擇器引擎介紹 。下面對其中的數據存儲、事件處理、異常請求ajax、動畫等進行簡單的介紹。node
(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節點和附加數據的關係,就顯得很是重要。很明顯,目前有兩種思路來解決這個問題:1)直接附加在DOM節點上;2)經過一個id來關聯DOM節點、附加數據。這兩種方法各有利弊:方法1的好處是DOM節點、附加數據在一塊兒,便於維護;方法2的好處是能夠避免相互依賴,從而避免內存泄露問題。jquery
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節點,這些節點就沒法被垃圾回收機制回收,從而引發內存泄露問題。
3. 異步Deferredajax
jQuery中關於異步的實現,大體上遵循Promise/A規範,爲何說是大體呢?chrome
內部實現比較曲折抽象,代碼晦澀難懂,主要是經過"once"、"memory"兩個參數進行控制:"once"決定異步的回調函數只能被執行一次;「memory」決定函數具備記憶動能,也就是異步事件完成之後,再綁定回調函數,回調函數會當即執行。編程
4. 事件處理json
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/undelagate/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][1]; hanle = $._data(cur, "events")[type]; hanle.apply(cur); }
5. 異步請求ajax
異步請求是jquery在整體能夠分爲三部分:核心實現、便捷方法、ajax全局事件。其中該模塊依賴於Deferred模塊提供的異步編程模塊,能夠方便進行回調函數的註冊,例如:
$(url, options).then(successFn, failFn);
其中核心方法的實現主要包括如下驟:
6. 動畫解析
在jQuery中,動畫show、hide、fadeIn、fadeOut等均要調用Animation方法,也就是說Animation是最基本的入口函數。在上圖中能夠看到該入口函數包括了三個過程:參數配置、生成動畫函數、動畫函數執行。下面展開包括如下細節:
固然,jQuery內部實現比較複雜,考慮了函數暫停、樣式臨時修改(修改inline元素的width/height時,會臨時將其display修改成inline-block)、清空動畫隊列等操做。