$(document).ready(fn) 是 jQuery 最標志性的 API,但其內部實現極具巧思:融合了事件監聽、狀態機、Deferred/Promise 異步模式、跨瀏覽器兼容層。本文從源碼 (v1.x/2.x 分支) 出發,繪製流程圖,逐行解析四種觸發入口、readyList 委托機制、completed 清理函數、detach 事件卸載、IE hack 等核心邏輯,還原 DOM Ready 的真實面貌。
所有寫法最終收斂到 rootjQuery.ready(selector) (即 $(document).ready(fn)):
$(fn) → jQuery.fn.init 判斷 selector 為 Function → rootjQuery.ready(fn)$(document).ready(fn) → 直接調用原型方法 ready$(document).on('ready', fn) → 事件綁定,最終在 jQuery.ready() 內觸發 jQuery(document).trigger('ready').off('ready')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'); // 執行後移除,防重複
}
}
});
readyListvar 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 實例。completed & detachvar 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') 綁定的回調
readyList 只有首次調用 ready.promise() 時創建,無用無開銷。jQuery.ready(),兼容四種調用風格。isReady 標誌 + detach() 移除監聽器,確保只觸發一次。readystatechange + doScroll 检測 (源碼中未完全展示) 解決 IE 無 DOMContentLoaded 問題。promise() 而非 Deferred,防止外部調用 resolve/reject 破壞狀態機。這段源碼是 jQuery 異步編程模型、跨瀏覽器事件處理、模塊化設計的教科書級範例。深度理解它,對掌握前端框架核心架構極有裨益。