jQuery 源码学习之路七(ready)

下面 3 种都是 $.ready 的语法
1
2
3
$(function(){});
$(document).ready(function(){});
$().ready(function(){});

在jQuery 3.0 中,只建议使用第一种语法,其他语法仍然能正常工作,但已被标记为弃用。这是因为,这些选择器跟.ready()方法的行为没有任何关系,这是低效的,并可能导致对该方法行为不正确的假设。
那么,为什么jquery要加一个这样的方法呢,直接用 winfdow.onload = function(){}; 来操作页面加载后再执行不好么。这就要看一下DOM文档加载的步骤:

1
2
3
4
5
6
(1) 解析HTML结构。
(2) 加载外部脚本和样式表文件。
(3) 解析并执行脚本代码。
(4) 构造HTML DOM模型。 //ready 越早处理 DOM ,对用户的体验越好
(5) 加载图片等外部文件。
(6) 页面加载完毕。 //window.onload

从上面 DOM 文档加载的步骤可知,用 $.ready 先在DOM结构树加载完之后开始做事了,不用等其他资源加载完毕。有利于用户体验。

源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// jQuery使用的ready异常处理方式是保证DOM刷新完毕再把异常抛出
jQuery.readyException = function( error ) {
window.setTimeout( function() {
throw error;
} );
};
// The deferred used on DOM ready
var readyList = jQuery.Deferred(); //jQuery.js加载时候就执行
jQuery.fn.ready = function( fn ) {
//在Deferred内部的then方法做了一个setTimeOut的操作,做了递归。
//所以保证了DOM肯定是完全加载,相当于监听了DOMContentLoaded
//此处[! Deferred的文章路径]
readyList
.then( fn ) // 先执行完deferred里面的内容再执行fn
// Wrap jQuery.readyException in a function so that the lookup
// happens at the time of error handling instead of callback
// registration.
.catch( function( error ) {
//若fn出错则调用jQuery的ready异常处理方法
jQuery.readyException( error );
} );
return this;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//从源码中摘取
//init = jQuery.fn.init = function( selector, context, root )
//$(function(){}); 调用时,root 为 undefined,故 root = rootjQuery;
//$(function(){}); 这种方式就是 $(document).ready(function(){});
rootjQuery = jQuery( document );
root = root || rootjQuery;
//在 selector 是个方法的情况下
else if ( jQuery.isFunction( selector ) ) {
//若 ready 存在时,就调用ready方法
return root.ready !== undefined ?
root.ready( selector ) :
// Execute immediately if ready is not present
// 否则立即执行
selector( jQuery );
}

上面是对$(document).ready()方法的解释,对应的,juqery 还有一个静态的方法 $.ready();那这个方法又可以做什么呢?
从jQuery 3.0开始, 可以通过jQuery.when 或者原生的 Promise.resolve() 使用这个对象。你的代码不应该假设这个对象是否是一个 jQuery.Deferred,原生的 Promise ,或者其他类型的promise对象。

使用 jQuery.when 监听 document ready
1
2
3
$.when( $.ready ).then(function() {
// Document is ready.
});
典型用法涉及另一个 promise ,使用 jQuery.when.
1
2
3
4
5
6
7
$.when(
$.getJSON( "ajax/test.json" ),
$.ready
).done(function( data ) {
// Document is ready.
// Value of test.json is passed as `data`.
});
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
jQuery.extend( {
// Is the DOM ready to be used? Set to true once it occurs.
// DOM 是否准备好,默认是false,一旦准备好,isReady的值被设置为 true
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
// 设置一个等待 DOM 准备好的计数器
readyWait: 1,
// Hold (or release) the ready event
// 保持或是释放就绪事件
holdReady: function( hold ) {
if ( hold ) {
jQuery.readyWait++; //若是在等待阶段,那么计数器就进行加法运算
} else {
jQuery.ready( true ); //已经不用等待,就直接调用 jQuery.ready 方法
}
},
// Handle when the DOM is ready
ready: function( wait ) {
// Abort if there are pending holds or we're already ready
// 已经加载完成,跳出
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
return;
}
// Remember that the DOM is ready
// 记录 DOM 已经准备好了
jQuery.isReady = true;
// If a normal DOM Ready event fired, decrement, and wait if need be
if ( wait !== true && --jQuery.readyWait > 0 ) {
return;
}
// If there are functions bound, to execute
readyList.resolveWith( document, [ jQuery ] );
}
} );
jQuery.ready.then = readyList.then;
// The ready event handler and self cleanup method
// 页面加载完毕,移除监听事件并调用 jQuery.ready 方法
function completed() {
document.removeEventListener( "DOMContentLoaded", completed );
window.removeEventListener( "load", completed );
jQuery.ready();
}
// Catch cases where $(document).ready() is called
// after the browser event has already occurred.
// Support: IE <=9 - 10 only
// Older IE sometimes signals "interactive" too soon
// 判断当前页面是否加载完成
if ( document.readyState === "complete" ||
( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
//异步处理以允许脚本有延迟准备的机会。
window.setTimeout( jQuery.ready );
} else {
// 添加监听事件
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", completed );
// A fallback to window.onload, that will always work
window.addEventListener( "load", completed );
}

2017-11-24