在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关的知识点:

(1)《浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序》主要针对向页面引入JavaScript代码的不同方法,研究了其在代码执行顺序方面的问题,特别重点研究了document.write这种方式引入JavaScript脚本时的执行顺序问题。

(2)《浏览器环境下JavaScript脚本加载与执行探析之defer与async特性》讲解了两个影响脚本加载与执行顺序的<script>标签元素特性:defer和async

(3)《浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入》又通过实验探究了动态脚本技术和Ajax脚本注入技术的代码执顺序问题,并与defer属性的使用做了结合的比较

本篇文章继续浏览器环境下JavaScript脚本加载与执行探析的话题,重点讲解DOMContentLoaded这一在实际编程中经常使用的事件。

1. DOMContentLoaded?

相信有经验的前端开发者对DOMContentLoaded事件已经很熟悉了,MDN的解释可以很清楚地表达这个事件的意思:

The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. A very different event - load - should be used only to detect a fully-loaded page. It is an incredibly popular mistake to use load where DOMContentLoaded would be much more appropriate, so be cautious.

重点阅读加粗的部分,也就是说,DOMContentLoaded是在HTML文档完全加载和解析结束时触发,而不会等待样式表、图片和子框架的加载。在实际编程中,我们通常都有这样一种需求,“文档加载结束时执行某些JS代码”,较早的时候,可能会使用window.onload=function(){}这样的方法,但是onload需要等待图片、样式表等资源加载结束才会触发,如果我们的网页包含较多的这些资源,那么用户必须等待这些资源加载结束后才能与页面进行交互,明显是有违用户体验的,这一点相信前端开发者也都比较熟悉了。

DOMContendLoaded虽好,但是兼容性还是有些问题:

可以看到,IE<=8是不支持DOMContentLoaded的,那么肯定会有相应的兼容方案,我们这里讲解jQuery的兼容方法

2. jQuery中的DOMContentLoaded兼容(以jQuery1.7.1为例)

2.1 jQuery.prototype.ready方法

我们都知道,在jQuery中,绑定DOMContentLoaded事件有以下两种方法:

(1)$(document).ready(function(){//要执行的代码})

(2)$(function(){//要执行的代码})

这两种写法是等价的,我们通过源码来看一下,直接写$(function(){})时,代码是如何运行的:

在jQuery1.7.1的196行:

         // HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}

当我们向jQuery的构造函数直接传入了一个函数时,会调用以上代码,而rootjQuery是什么呢?我们继续从源码中找答案:

在源码的第916行:

 // All jQuery objects should point back to these
rootjQuery = jQuery(document);

这样对应起来,就可以知道为什么这两种写法是等价的了。

2.2 jQuery脚本初始化时做了什么

在源码的第83行,有如下的声明代码:

     // The ready event handler
DOMContentLoaded,

这里声明了一个ready事件的处理器,并从脚本的第919行开始,对DOMContentLoaded处理器进行了定义:

 // Cleanup functions for the document ready method
if ( document.addEventListener ) {
DOMContentLoaded = function() {
document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
jQuery.ready();
}; } else if ( document.attachEvent ) {
DOMContentLoaded = function() {
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( document.readyState === "complete" ) {
document.detachEvent( "onreadystatechange", DOMContentLoaded );
jQuery.ready();
}
};
}

这里的兼容性写法相信不难理解,DOMContentLoaded被定义为一个函数,函数的功能是调用jQuery的静态方法:ready方法,同时如果浏览器支持addEventListener,那么则移除DOMContentLoaded的监听,否则如果是IE<=8,则移除document的readystatechange事件的监听。document的readyState可以参考:https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState

在这段的后面,紧接着定义了一个doScrollCheck方法,这个方法是在低版本IE中辅助判断DOMContentLoaded的方法:

 // The DOM ready check for Internet Explorer
function doScrollCheck() {
if ( jQuery.isReady ) {
return;
} try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll("left");
} catch(e) {
setTimeout( doScrollCheck, 1 );
return;
} // and execute any waiting functions
jQuery.ready();
}

这个方法首先判断jQuery.isReady状态,如果isReady为true,表示ready事件已经被触发,无需再进行检测。如果在一个try-catch代码块中调用document.documentElment.doScroll方法,如果这个方法不报错,那么就表示document已经完成了加载和构建,功能上类似于DOMContentLoaded;如果报错,则在catch块中连续调用自身,直到方法不报错为止,然后调用jQuery.ready方法。

2.3 jQuery.prototype.ready方法到底做了什么

2.2节中介绍的代码是jQuery脚本初始化时就会执行的代码,也就是说,在我们引入了jQuery脚本,并且浏览器解析到了相应的<script>标签,浏览器就会去下载脚本并执行脚本代码,此时,2.2节中的代码就已经执行。然后,如果我们在之后的脚本中,使用2.1节介绍的方法去绑定ready事件的处理函数,则首先会调用jQuery.prototype.ready方法,该方法定义在第273行:

     ready: function( fn ) {
// Attach the listeners
jQuery.bindReady(); // Add the callback
readyList.add( fn ); return this;
},

首先调用了jQuery的静态方法bindReady,然后将回调函数加入到readyList中,readyList专门用来存储DOMContentLoaded事件的延迟函数,最后函数返回了this。

2.4 jQuery.bindReady方法

这个方法用来初始化ready事件函数列表,并为document绑定DOMContentLoaded事件,我们来看一下代码:

     bindReady: function() {
if ( readyList ) {
return;
} readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
return setTimeout( jQuery.ready, 1 );
} // Mozilla, Opera and webkit nightlies currently support this event
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame
// continually check to see if the document is ready
var toplevel = false; try {
toplevel = window.frameElement == null;
} catch(e) {} if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
},

代码首先判断readyList是否初始化过了,没有初始化才继续执行。

readyList = jQuery.Callbacks( "once memory" );

这一句代码创建一个监听函数列表,这里用到了jQuery中的底层支持模块:Callbacks,向方法传入的两个flag的含义为(可以参考《jQuery技术内幕 深入解析jQuery架构设计与实现原理》4.1节):

once:表示监听函数列表readyList只能被触发一次;记录上一次触发readyList时的参数,之后添加的任何监听函数都将用记录的参数值立即调用 。

随后开始为document绑定DOMContentLoaded,第一个if分支很好理解,第一个if分支的最后一行增加load事件的监听是一种防御性编程,同时我们在创建readyList时使用了once标记,这样即使load事件触发后,readyList中已经执行过的函数都不会再执行。

如果在IE<=8中,也就是if的第二个分支,那么则同时使用两种方法模拟判断DOMContentLoaded,一种是监听readyState是否为"complete",一种则使用doScrollCheck方法,两种方法只要有一种满足条件,就会触发jQuery.ready。

2.5 jQuery.ready方法

接下来的核心就是jQuery.ready方方法了,我们首先看一下代码:

     // Handle when the DOM is ready
ready: function( wait ) {
// Either a released hold or an DOMready/load event and not yet ready
if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( !document.body ) {
return setTimeout( jQuery.ready, 1 );
} // Remember that the DOM is ready
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.fireWith( document, [ jQuery ] ); // Trigger any bound ready events
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger( "ready" ).off( "ready" );
}
}
},

这个函数有一个参数wait,主要用来处理ready事件的延迟和重新触发,它和jQuery.readyWait属性关联,这个属性默认值为1,我们暂不考虑延迟的情况,就把wait视为undefined,readyWait为1,在这种情况下,我们的代码符合第一个if条件,第6行的代码判断document.body,目的是保证document.body存在,然后再把jQuery.isReady设为true,表示document已经加载和解析完毕。第14行的if条件在这里也同样满足,接下来就通过readyList.fireWith执行readyList中的函数列表,第一个参数document指定了函数的上下文,第二个参数则指定了ready监听函数的参数。接下来的代码执行通过jQuery(document).on("ready",fn)绑定的函数。

3 补充说明

在2.1节中提到,在jQuery中,绑定DOMContentLoaded事件由两种方法,事实上,在阅读完相应的源码之后,应该可以看到,其实还有第三种方法:

(3)$(document).on("ready", fn)

这种方法绑定的ready函数与前两种方法有些不同,如果在DOMContentLoaded事件已经触发之后,通过这种方法绑定的代码是不会再执行的,而通过前两种方法绑定的函数仍然可以执行,究其原因还是在于jQuery构建了自身的一套ready事件绑定和执行体系,Query( document ).trigger( "ready" )这段代码只在jQuery.ready方法中才会调用,而jQuery.ready方法只会在DOMContentLoaded事件触发之后才会执行(或document.readyState===“complete”或doScrollCheck不再报错),如果在DOMContentLoaded触发再绑定,那么函数时不会执行的(这与js中的事件机制是相同的,如果错过了事件再去监听,那么是不会有结果的)

浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded的更多相关文章

  1. 浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入

    在<浏览器环境下JavaScript脚本加载与执行探析之defer与async特性>中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机.浏览器支持情况.浏览器bug以及 ...

  2. 浏览器环境下JavaScript脚本加载与执行探析之defer与async特性

    defer和async特性相信是很多JavaScript开发者"熟悉而又不熟悉"的两个特性,从字面上来看,二者的功能很好理解,分别是"延迟脚本"和"异 ...

  3. 浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序

    本文主要基于向HTML页面引入JavaScript的几种方式,分析HTML中JavaScript脚本的执行顺序问题 1. 关于JavaScript脚本执行的阻塞性 JavaScript在浏览器中被解析 ...

  4. 浏览器中Javascript的加载和执行

    在刚学习Javascript时曾对该问题在小组内做个一次StudyReport,发现其中的基础还是值得分析的. 从标题分析,可以加个Javascript的加载和执行分为两个阶段:加载.执行.而加载即浏 ...

  5. 无网络环境下使用docker加载镜像

    无网络环境下使用docker加载镜像 你需要做的主要有3步骤:   先从一个有网络的电脑下载docker镜像 [root@localhost ~]# docker pull hub.c.163.com ...

  6. JS脚本加载与执行对性能的影响

    高性能JavaScript-JS脚本加载与执行对性能的影响 在web产品优化准则中,很重要的一条是针对js脚本的加载和执行方式的优化.本篇文章简单描述一下其中的优化准则. 1. 脚本加载优化 1.1 ...

  7. 前端性能优化:细说JavaScript的加载与执行

    本文主要是从性能优化的角度来探讨JavaScript在加载与执行过程中的优化思路与实践方法,既是细说,文中在涉及原理性的地方,不免会多说几句,还望各位读者保持耐心,仔细理解,请相信,您的耐心付出一定会 ...

  8. JavaScript脚本加载相关知识

    <script>标签的位置 HTML4规范允许<script>可以放在<head>或<body>中. 但是,放在<head>中会导致性能问题 ...

  9. 高性能JavaScript-JS脚本加载与执行对性能的影响

    在web产品优化准则中,很重要的一条是针对js脚本的加载和执行方式的优化.本篇文章简单描述一下其中的优化准则. 1. 脚本加载优化 1.1 脚本位置对性能的影响 优化页面加载性能的原则之一是将scri ...

随机推荐

  1. js 文件下载 进度条

    js: /** * 下载文件 - 带进度监控 * @param url: 文件请求路径 * @param params: 请求参数 * @param name: 保存的文件名 * @param pro ...

  2. Bootstrap学习遇到的role属性--- 无障碍网页应用属性

    以前接触过Bootstrap,但也只是仅仅接触,现在重新学习下,今天看到一个例子中的属性有一个role, 查阅资料发现这个是--WAI-ARIA无障碍设计属性: 通俗说是该设计为了一些盲人,失聪,残疾 ...

  3. hbase shell 命令

    HBase使用教程 时间 2014-06-01 20:02:18 IT社区推荐资讯 原文  http://itindex.net/detail/49825-hbase 主题 HBase 1     基 ...

  4. 【转】你知道C#中的Lambda表达式的演化过程吗?

    [转]你知道C#中的Lambda表达式的演化过程吗? 那得从很久很久以前说起了,记得那个时候... 懵懂的记得从前有个叫委托的东西是那么的高深难懂. 委托的使用 例一: 什么是委托? 个人理解:用来传 ...

  5. javascript的use strict(使用严格模式)

    上一篇博文学习变量声明带var和不带的区别.搜索相关的文章. 引出了另一个概念. "use strict" 使用严格模式 对于一个使用者而不是概念研究者,我觉得没有意义争论或者讨论 ...

  6. mybatis学习 十二 多表查询

    Mybatis 实现多表查询方式: (1)业务装配.对两个表编写单表查询语句,在业务(Service)把查询的两个结果进行关联. (2)使用Auto Mapping特性,在实现两表联合查询时通过别名完 ...

  7. 求先序排列(NOIP2001&NOIP水题测试(2017082301))

    题目链接:求先序排列 这道题讲白了,就是数的构造,然后遍历. 思路大致是这样: 我们先通过后序遍历,找到当前区间的根,然后在中序遍历中找到根对应的下标,然后就可以分出左右子树,建立当前根与左右子树根的 ...

  8. GUI的优化操作/添加背景图片等

    一.背景图片的添加这是JAVA中添加背景图片的方式,基本思路先建立一个Label标签,然后建立一个层次的布局,将label标签添加到最下面去. ImageIcon image=new ImageIco ...

  9. 2018.11.08 NOIP模拟 水管(简单构造)

    传送门 仔细读题会发现只要所有点点权之和等于0一定有解. 如何构造? 直接当做树来构造就行了,非树边都赋值成0就行. 代码

  10. IntelliJ IDEA 2017版 spring-boot 2.03后 Pageable用法;Pageable用法,PageRequest过时,新用法;Pageable过时问题;

    1.旧版本Pageable用法: 但是会显示,这个版本已经过时,这时可以查看源码. 一般,一个方法过时,就会在其附近形成一个新的同名的但是其他用法的方法.按照这个理念,来找这个源码.很幸运,蒙对了,我 ...