萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> 資料庫綜合

jQuery 源碼級剖析:DOM Ready 機制的四種觸發路徑與 Deferred/Promise 實現全景圖

$(document).ready(fn) 是 jQuery 最標志性的 API,但其內部實現極具巧思:融合了事件監聽、狀態機、Deferred/Promise 異步模式、跨瀏覽器兼容層。本文從源碼 (v1.x/2.x 分支) 出發,繪製流程圖,逐行解析四種觸發入口、readyList 委托機制、completed 清理函數、detach 事件卸載、IE hack 等核心邏輯,還原 DOM Ready 的真實面貌。

一、 四種觸發入口:殊途同歸

所有寫法最終收斂到 rootjQuery.ready(selector) (即 $(document).ready(fn)):

  1. $(fn) → jQuery.fn.init 判斷 selector 為 Function → rootjQuery.ready(fn)
  2. $(document).ready(fn) → 直接調用原型方法 ready
  3. $(document).on('ready', fn) → 事件綁定,最終在 jQuery.ready() 內觸發 jQuery(document).trigger('ready').off('ready')
  4. jQuery.ready.promise().done(fn) → 直接掛載回調到內部 Deferred 物件

二、 核心狀態機:jQuery.extend 靜態屬性

jQuery.extend({
    isReady: false,      // DOM 是否就緒標誌
    readyWait: 1,        // 預加載計數器 (holdReady 控制)
    holdReady: function(hold) { // 鎖定/釋放就緒
        hold ? jQuery.readyWait++ : jQuery.ready(true);
    },
    ready: function(wait) {     // 核心就緒執行入口
        // 1. 條件檢查:有 hold 或已就緒則退出
        if (wait === true ? --jQuery.readyWait : jQuery.isReady) return;

        // 2. IE hack: body 不存在則定時重試
        if (!document.body) return setTimeout(jQuery.ready);

        // 3. 標記就緒
        jQuery.isReady = true;

        // 4. 釋放所有 hold
        if (wait !== true && --jQuery.readyWait > 0) return;

        // 5. 核心:觸發 readyList 委托回調
        readyList.resolveWith(document, [jQuery]);

        // 6. 觸發綁定在 document 上的 'ready' 事件 (兼容 .on('ready') 寫法)
        if (jQuery.fn.trigger) {
            jQuery(document).trigger('ready').off('ready'); // 執行後移除,防重複
        }
    }
});

三、 Deferred/Promise 委托核心:readyList

var readyList;

jQuery.ready.promise = function(obj) {
    if (!readyList) {
        readyList = jQuery.Deferred(); // 創建 Deferred 物件 (延遲對象)
        // 捕獲原生 DOMContentLoaded / load / readystatechange
        // 綁定 completed 回調
        ...
    }
    return readyList.promise(obj); // 返回 Promise 接口 (只读)
};

模式解讀:

  • 單例模式:readyList 全局唯一,首次調用創建。
  • 觀察者模式:.done(fn) 添加觀察者;resolveWith 通知所有觀察者執行。
  • 上下文綁定:resolveWith(document, [jQuery]) 使回調內 this === document,參數接收 jQuery 實例。

四、 跨瀏覽器 DOM 就緒檢測與清理:completed & detach

var completed = function(event) {
    // 條件:標準瀏覽器 DOMContentLoaded / 任何瀏覽器 load / IE readystatechange=complete
    if (document.addEventListener || event.type === 'load' || document.readyState === 'complete') {
        detach(); // 1. 立即卸載所有監聽器,防重複觸發
        jQuery.ready(); // 2. 觸發就緒流程
    }
};

var detach = function() {
    if (document.addEventListener) { // 標準 W3C
        document.removeEventListener('DOMContentLoaded', completed, false);
        window.removeEventListener('load', completed, false);
    } else { // IE <= 8
        document.detachEvent('onreadystatechange', completed);
        window.detachEvent('onload', completed);
    }
};

// 綁定邏輯 (簡化)
if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded', completed, false);
    window.addEventListener('load', completed, false); // 兜底
} else {
    document.attachEvent('onreadystatechange', completed);
    window.attachEvent('onload', completed); // 兜底
    // IE iframe doScroll 檢測技巧 (源碼中另有實現,此處省略)
}

五、 流程圖總結 (文本版)

用戶調用 $(fn) / $(doc).ready(fn) / $(doc).on('ready',fn) / $.ready.promise().done(fn)
            ↓
    jQuery.ready.promise() → 惰性創建 readyList (Deferred) → 綁定原生就緒事件 (DOMContentLoaded/load/readystatechange)
            ↓
瀏覽器觸發原生就緒事件 → completed() → detach() 卸載事件 → jQuery.ready()
            ↓
jQuery.ready() → 檢查 holdReady/body → 標記 isReady=true → readyList.resolveWith(document, [jQuery])
            ↓
    ├─ 所有 .done(fn) 回調按序執行 (this=document)
    └─ jQuery(document).trigger('ready').off('ready') → 執行 .on('ready') 綁定的回調

六、 關鍵設計亮點

  1. 惰性初始化:readyList 只有首次調用 ready.promise() 時創建,無用無開銷。
  2. 多路觸發融合:統一入口 jQuery.ready(),兼容四種調用風格。
  3. 防重複執行:isReady 標誌 + detach() 移除監聽器,確保只觸發一次。
  4. IE 兼容性:readystatechange + doScroll 检測 (源碼中未完全展示) 解決 IE 無 DOMContentLoaded 問題。
  5. Promise 接口隔離:暴露 promise() 而非 Deferred,防止外部調用 resolve/reject 破壞狀態機。

這段源碼是 jQuery 異步編程模型、跨瀏覽器事件處理、模塊化設計的教科書級範例。深度理解它,對掌握前端框架核心架構極有裨益。

copyright © 萬盛學電腦網 all rights reserved