1、浏览器渲染引擎的HTML解析流程

何谓“渲染”,其实就是浏览器把请求到的HTML内容显示出来的过程。渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。下面是渲染引擎在取得内容之后的基本流程:

1,解析html以构建dom树(构建DOM节点):渲染引擎开始解析html,并将标签转化为内容树中的dom节点。

2,构建render树(解析样式信息):解析外部CSS文件及style标签中的样式信息。Render树由一些包含有各种属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。

3,布局render树(布局DOM节点):执行布局过程,它将确定每个节点在屏幕上的确切坐标。

4,绘制render树(绘制DOM节点):Render树构建好了之后,将会再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

以上就是HTML渲染的基本流程,但这并不包含解析过程中浏览器加载外部资源如图片、脚本、iframe等的过程。说白了,上面的四步仅仅是HTML结构的渲染流程,而外部资源的加载在HTML结构的渲染流程中贯穿始终,即便绘制DOM节点已经完成,外部资源依然可能正在加载中或尚未加载。

2、window.onload

了解了浏览器渲染引擎的HTML解析流程,我们就回到domReady。前文提到了,那个蛋疼的TypeError是由于在DOM树构建完成之前对节点进行了操作,而通常的解决的办法就是让js在window.onload的回调里执行,也就是说,在文档所有的解析渲染、资源加载完成之前,不让js脚本执行,这样一来就妥妥地避免了因js操作先于DOM树创建而带来的bug:

1 Window.onload = function(){
2 //doSomething
3 }

这样的解决办法应该是初学原生js时很多人最常用的解决办法,看起来也的确没什么问题。如果文档外部资源不多的时候也没什么问题,但,我们来做一个假设。假设一个页面上有100张远程图片,我需要让js做到在点击每张图片时alert出图片的src属性,又该怎么做?

是不是已经发现点小问题了?按照第二部分内容对浏览器解析渲染HTML流程的介绍,DOM树很快就构建完毕了,而100张图片还在缓慢地加载。而要想执行alert出图片src属性的js,则需要等到100张图片全部加载完成后才能执行。而在这期间,页面元素不会响应你的任何操作,就好像“死”了一样。如果是在实际项目中,用户很可能不会等到你页面所有东东加载完以后才去操作,在面对一个不会对自己的操作做任何响应的页面,唯一比较解气的方式就是——果断关掉~然后……就没有了然后。

所以在实际应用中,我们经常会遇到这样的场景,让页面加载后去做一些事情:绑定事件、DOM操作某些结点等。使用window.onload对于很多实际的应用而言有点太“迟”了,比较影响用户体验。那有没有更好的方法解决这个问题?比如提前到只要DOM树创建完成之后就可以进行如上操作呢?答案当然是有的:DOMContentLoaded事件

3、DOMContentLoaded

说这个之前必须要提一下jQuery中的domReady机制。很多时候在使用jq也会出现最前面出现的那个TypeError,解决办法就是把js放到jQuery的ready回调里:

1 $(document).ready(function(){...});

或者:

1 $(function(){...});

这样一来,错误妥妥地没了。然后对比因果关系,大概得出一个结论:jQuery的ready回调应该跟window.onload的效果原理是一样的。恩,应该是这样。那我们就先来看一看jQuery(1.11.1)的ready回调是如何实现的:

 1 jQuery.fn.ready = function( fn ) {2     // Add the callback3     jQuery.ready.promise().done( fn );4     return this;5 };6 jQuery.ready.promise = function( obj ) {7     if ( !readyList ) {8         readyList = jQuery.Deferred();9         if ( document.readyState === "complete" ) {
10             setTimeout( jQuery.ready );
11         } else if ( document.addEventListener ) {
12             document.addEventListener( "DOMContentLoaded", completed, false );
13             window.addEventListener( "load", completed, false );
14         } else {
15             document.attachEvent( "onreadystatechange", completed );
16             window.attachEvent( "onload", completed );
17             var top = false;
18             try {
19                 top = window.frameElement == null && document.documentElement;
20             } catch(e) {}
21             if ( top && top.doScroll ) {
22                 (function doScrollCheck() {
23                     if ( !jQuery.isReady ) {
24                         try {
25                             // Use the trick by Diego Perini
26                             top.doScroll("left");
27                         } catch(e) {
28                             return setTimeout( doScrollCheck, 50 );
29                         }
30                         detach();
31                         jQuery.ready();
32                     }
33                 })();
34             }
35         }
36     }
37     return readyList.promise( obj );
38 };

看起来比想象中的window.onload要复杂呵。Jq的源码中出现了DOMContentLoaded、readyState、onreadystatechange,这些跟domReady有什么关系?

我们还是先从DOMContentLoaded说起吧。就如前面所述,很多时候我们会把js逻辑写在window.onload回调中,以防DOM树还没有建完就开始对节点进行操作从而导致错误,而对于很多实际应用来说,越早介入对DOM的干涉就越好,比如进行特征侦测、事件绑定、DOM操作神马的。domReady还可以满足用户提前绑定事件的需求,因为有些情况下页面的图片等外部资源过多,window.onload迟迟不能触发,这时若还没有绑定事件,用户点任何的按钮都没反应(链接除外)会直接影响体验。

为了解决window.onload的短板,FF中便增加了一个DOMContentLoaded方法,与onload相比,DOMContentLoaded方法触发的时间更早,它是在页面的DOM树创建完成后(也就是HTML解析第一步完成)即触发,而无需等待其他资源的加载。Webkit引擎从版本525(Webkit nightly 1/2008:525+)开始也引入了该事件,Opera中也包含该方法。到目前为止NB的IE仍然没有要添加的意思。虽然IE下没有,但解决办法总是有的。于是对于那些忙前忙后的兼容小达人和死不悔改的顽固派,也就有了两套策略:

1)支持DOMContentLoaded事件的,就使用DOMContentLoaded事件;

2)不支持的,就用来自Diego Perini发现的著名Hack兼容。兼容原理大概就是,通过IE中的document.documentElement.doScroll(‘left’)来判断DOM树是否创建完毕。

Blabla了这么多,来看个IE模拟DOMContentLoaded例子吧。这个例子就来自上面发现IE下doScroll Hackd的作者,细看也就是简化版的jQuery.ready回调的IE处理逻辑。

 1 function IEContentLoaded (w, fn) {2     var d = w.document, done = false,3     // 只执行一次用户的回调函数init()4     init = function () {5         if (!done) {6             done = true;7             fn();8         }9     };
10     (function () {
11         try {
12             // DOM树未创建完之前调用doScroll会抛出错误
13             d.documentElement.doScroll('left');
14         } catch (e) {
15             //延迟再试一次~
16             setTimeout(arguments.callee, 50);
17             return;
18         }
19         // 没有错误就表示DOM树创建完毕,然后立马执行用户回调
20         init();
21     })();
22     //监听document的加载状态
23     d.onreadystatechange = function() {
24         // 如果用户是在domReady之后绑定的函数,就立马执行
25         if (d.readyState == 'complete') {
26             d.onreadystatechange = null;
27             init();
28         }
29     };
30 }                

而对于高大上的chrome、ff等高级浏览器来说,对DOMContentLoaded事件的处理就相对来说小case了,按照标准的事件绑定方式就可以处理:

1 if ( document.addEventListener ) {
2     document.addEventListener( "DOMContentLoaded", completed, false );
3 }

五、实例

看到这,想必大家已经对DOMContentLoaded已经有了新的认识,onload保险丝也该适时换成智能电门啦~接下来就来个鲜活的例子,来让大家更清晰的做下对比。

首先,页面上有一组图片:

1 <ul>
2     <li><img src="img/01.jpg" /></li>
3     <li><img src="img/02.jpg" /></li>
4     <li><img src="img/03.jpg" /></li>
5     <li><img src="img/04.jpg" /></li>
6     <li><img src="img/05.jpg" /></li>
7 </ul>

页面的js处理逻辑:

 1 <script>2     var d = document;3     var msgBox = d.getElementById("showMsg");4     var imgs = d.getElementsByTagName("img");5     var time1 = null,time2 = null;6     if(d.addEventListener){7         d.addEventListener("DOMContentLoaded",domReady,false);8     }else{9         IEContentLoaded(domReady);
10     }
11     function domReady(){
12         msgBox.innerHTML += "dom已加载!<br>";
13         time1 = new Date().getTime();
14         msgBox.innerHTML += "时间戳:" + time1 + "<br>";
15     }
16
17     //兼容IE的domReady
18     function IEContentLoaded(fn){
19         var done = false,
20         init = function(){
21             if(!done){
22                 done = true;
23                 fn();
24             }
25         };
26         (function(){
27             try {
28                 d.documentElement.doScroll('left');
29             }catch(e){
30                 setTimeout(arguments.callee,50);
31                 return;
32             }
33             init();
34         })();
35         d.onreadystatechange = function(){
36             msgBox.innerHTML += "加载状态:" + d.readyState + "<br>";
37             if(d.readyState == 'complete'){
38                 d.onreadystatechange = null;
39             }
40         }
41     }
42     window.onload = function(){
43         msgBox.innerHTML += "onload已加载!<br>";
44         time2 = new Date().getTime();
45         msgBox.innerHTML += "时间戳:" + time2 + "<br>";
46         msgBox.innerHTML +="domReady比onload快:" + (time2 - time1) + "ms<br>";
47     }
48 </script>

相信js脚本不用做过多解释,在前面都已做过详细分析,我们直接来看运行结果:

很容易就能看出,DOMContentLoaded执行5238ms之后才执行的onload。这只是一个DEMO的差距,而如果是更大型的应用,可能这个时间差距会更大。

转载于:https://www.cnblogs.com/wang985850293/p/5231400.html

JS 实现 jQuery的$(function(){});相关推荐

  1. 解决sea.js引用jQuery提示$ is not a function的问题

    解决sea.js引用jQuery提示$ is not a function的问题 参考文章: (1)解决sea.js引用jQuery提示$ is not a function的问题 (2)https: ...

  2. pdfh5.js 基于pdf.js和jQuery,web/h5/移动端PDF预览手势缩放插件。

    pdfh5.js 基于pdf.js和jQuery,web/h5/移动端PDF预览手势缩放插件. 注意:本地绝对路径地址不能加载,跨域问题用代理或者服务端解决. svg模式渲染存在缺陷,只能渲染普通pd ...

  3. 返回顶部的js实现(jQuery/MooTools)

    一.引言 在web页面中,如果页面较高,为了方便用户快速地返回顶部,都会添加一个返回顶部按钮.例如: 如果你看到这段文字,说明转自<一棵树-博客园>,原文链接:http://www.cnb ...

  4. 连连看html游戏全代码js、jquery操作

    连连看html游戏全代码js.jquery操作 运行图片 目录路径 连连看水果方块版.html 连连看算法 进行下一个游戏的开发! 注意事项 我会把html文件.css文件提供下载地址,文件夹路径也展 ...

  5. 飞机大战html游戏全代码js、jquery操作

    飞机大战html游戏全代码 博主的话 运行图片 目录路径 飞机大战.html style.css 进行下一个游戏的开发! 注意事项 博主的话 当时博主只会html,css和原生JavaScript,假 ...

  6. 事件委托技术原理和使用(js,jquery)

    事件委托技术原理和使用(js,jquery) 原创 2016年03月10日 11:18:56 标签: 技术 / jquery / javascript 2555 一:事件委托技术原理 摘自http:/ ...

  7. 获得焦点时选择文本框的所有内容(Vanilla JS或jQuery)

    本文翻译自:Select all contents of textbox when it receives focus (Vanilla JS or jQuery) 什么是Vanilla JS或jQu ...

  8. JS、JQuery和ExtJs的跨域处理

    1.什么是跨域? 跨域,JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象.简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a ...

  9. html tab与jQuery,js与jquery分别实现tab标签页功能的方法

    本文实例讲述了js与jquery分别实现tab标签页功能的方法.分享给大家供大家参考,具体如下: 首先列出样式和html标签 *{margin: 0;padding: 0;} #myul li {li ...

最新文章

  1. 机器学习、深度学习、自然语言处理、计算机视觉顶级期刊的论文资料分享(附顶会论文下载链接)...
  2. Cisco easy *** basic ASA
  3. php sql 时间 函数,PHP模拟SQL Server的两个日期处理函数
  4. poj 2723 2-SAT问题
  5. java 创建数组工具类_用Java创建数组工具类ArrayTool
  6. Mysql之INFORMATION_SCHEMA解析1
  7. 【物流选址】基于matlab粒子群算法求解多物流中心选址问题【含Matlab源码 1458期】
  8. 本地方法接口和本地方法栈总结
  9. 开零食店能赚钱吗?有何经营技巧?
  10. java设置窗体图标_在java中怎么设置窗体的图标?详细步骤图解
  11. 外挂的定义、分类及实现原理
  12. 软件系统上线前演示剧本
  13. 黑马程序员--安卓22期毕业19天就业50人平均薪资8261
  14. java 根据已有ppt模板修改其内容
  15. Unable to prase template Class Error message: Index 0 out of bounds for length 0
  16. CentOS7和Ubuntu18.10下运行Qt Creator出现cannot find -lGL的问题的解决方案
  17. Android吃鸡 3dtouch,绝地求生刺激战场3Dtouch怎么用 3Dtouch安卓手机可以用吗
  18. mysql里一个中文汉字占多少字节数?
  19. 单片机与PC机的交流———基于STM32的串口通信
  20. JavaScript 设计模式之发布-订阅模式(上)

热门文章

  1. android toolbar源码解析,深入理解Android 5.0中的Toolbar
  2. java切换jdk版本_切换JDK版本quick
  3. (STL,vector)木块问题
  4. 上传附件_留学落户|上传附件预审时一定一定要注意的问题!
  5. 理解计算机网络的拓扑结构,认识计算机网络拓扑结构
  6. 基于Java jsp+servlet超市订单管理平台设计和实现【建议收藏】
  7. 判断输入的日期字符串是否小于当前日期
  8. MySQL修改数据表
  9. python基础入门(7)之元组
  10. netbeans java9_Java 开发新选择?Apache NetBeans IDE 9.0 备受好评