`
zhoulujun007
  • 浏览: 17082 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载

阅读更多

浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询、监听用户事件。

这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞。两个很明显的阻塞就是:脚本执行时对其他线程的阻塞脚本加载时对其他线程的阻塞

这两个阻塞发生在HTML页面初次解析时,它们对性能的影响较大,原因是:

document对象绑定了一个事件:DOMContentLoaded。这个事件会在DOM解析完成之后触发。这个事件触发之后(而不是window.load事件),会进入异步事件驱动阶段(另一个线程控制)。也就是说,DOM解析工作不完成,用户与页面的很多(并不是所有)事件交互就无法进行。这时候浏览器的忙指示(那个页面上方的烦人的旋转的圆圈)不会消失。

 

DOMContentLoaded什么时候触发?

DOMContentLoaded事件本身不会等待CSS文件、图片、iframe加载完成。

DOMContentLoaded的触发时机是:加载完页面,解析完所有标签(不包括执行CSS和JS),但是JS的执行,需要等待位于它前面的CSS加载(如果是外联的话)、执行完成,因为JS可能会依赖位于它前面的CSS计算出来的样式。所以:

  • 如果页面中没有script标签,DOMContentLoaded事件并没有等待CSS文件、图片加载完成。

  • 如果页面中静态的写有script标签,DOMContentLoaded事件需要等待JS执行完才触发。而且script标签中的JS需要等待位于其前面的CSS的加载完成

注:现代浏览器会并发的预加载CSS、JS、IMG(例如:当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载)。但是,执行CSS和JS的顺序还是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)——先加载完成的资源,如果其依赖还没加载、执行完,就只能等着。

v2-e4744784c328c5c7a1527ff0822c1a5d_hd.jpg

所以就造成外部资源阻塞渲染,如CSS 与 JavaScript

  • 默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。

  • JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性。

默认情况下,CSS 被视为阻塞渲染的资源,存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。

总结如下:

  • css加载不会阻塞DOM树的解析

  • css加载会阻塞DOM树的渲染

  • css加载会阻塞后面js语句的执行

css会阻塞js,同理,css也会阻塞img解码、paint(浏览器认为你的CSS没有加载完毕,不确定图片的样式到底如何,牵扯到重绘资源问题),js不会阻塞img的解码、paint(估计chrome做了优化,具体本人还不知,希望客官补充)。

css阻塞优化:

  • 还可以用媒体类型(media type)和媒体查询(media query)来解除对渲染的阻塞。

    media=“print",会加载,但不会阻塞;media="(min-width:320px)",会在符合查询条件下阻塞(适配css会执行)

  • 大css文件拆分成多个小css文件,并发加载

因为渲染线程和js线程与资源进行加载的线程并不互斥,不会互斥意味着:资源的加载可以和UI渲染、重排,事件响应,或者JavaScript代码的执行的并发进行

所以资源加载器线程会一直进行并发加载。

这里还有一个知识点:下载的最大并行数指的是从一个主机上下载的最大并行数,如果从多个主机下载资源,这个数量会翻倍,但是由于对DNS的解析也是一个性能优化的点,故而一般策略是:不应设置超过4个主机,最好只设置2个主机。

但是操蛋的就是,如果浏览器解析DOM时需要下载脚本资源,那么下载这个资源的线程就是阻塞其他下载线程以及渲染线程,导致渲染速度变慢。

但是假设该脚本下载的速度较慢,而且多个脚本非并发下载,并且假如多个<script>内脚本执行时间较长的话,DOM解析工作还是会一直完不成。

故而我们需要无阻塞加载脚本的技术。

 

js阻塞优化

因为:脚本执行和渲染DOM的并发可能会引发严重的冲突(脚本可以修改DOM)

所以:JavaScript引擎和渲染引擎所在的两个线程被设计为互斥的!

这就意味着:在执行<script>中内容时,浏览器会切换到JavaScript引擎所在的线程,此时渲染引擎所在的线程会阻塞,故其后元素的解析和渲染会暂停。这时候如果脚本执行时间太长的话,不仅后面的元素会一直看不到,对DOM的解析工作也会一直完不成。用户会陷入焦急的等待中。

为了防止javascript阻塞,我们会

1、把<script>放到紧跟</body>之前的位置

        这样就不会影响需要放到页面上的UI元素的解析了。这样的好处就是,用户能即使看到页面上的UI元素,而防止出现了浏览器白屏等现象。

2、动态脚本元素-不重要的js动态插入

        因为document.createElement("script")的async属性默认为true,而document.head.appendChild代码之后,由于没有触发渲染树的重绘,切换回的渲染线程会将剩下的DOM解析并渲染完毕。同时新插入的<script>中的资源也会并发的下载。

var script=document.createElement("script");
console.log(script.async);//true

        同理:用XHR对象下载代码,并注入到页面也可以达到同样的效果

        如果需要同步执行,需要将async属性设置为fasle

3、h5时代,script添加defer或asyn两个属性(html4.0中定义了defer;html5.0中定义了async)

 

  • 如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。即:整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。defer不会改变script中代码执行顺序

  • 如果 script 标签中包含 async,则 HTML 文档构建不受影响,不需要等待 async-script 执行。但是,async-script 加载完成后,就会立即执行!如果页面还是没有解析完成,就会停下来(阻塞页面)等此脚本执行完毕再继续解析。async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。而且:多个 async-script 的执行顺序是不确定的

 

 

document.readyState

 

说道DOMContentLoaded,不得不提readystatechange,通过document.readyState值来更进一步来判断文档状态:

  1. uninitiated:xml 对象被产生,但没有任何文件被加载。

  2. loading:document正在下载,文件尚未开始解析。

  3. loaded:部分的文件已经加载且进行解析,但对象模型尚未生效。

  4. interactive:document完成了解析,但是资源还在下载,对象模型是有效但只读的

  5. complete:代表加载成功,文档加载完成,并且所有resource都加载完毕

通过下面代码验证,在chrome上貌似只有  interactivecomplete。

document.addEventListener("DOMContentLoaded",function () {
    console.log("DOMContentLoaded"+new Date())
});
        document.addEventListener("readystatechange",function () {
            console.log("B_____"+new Date());
            console.log(document.readyState)
//            switch (document.readyState){
//                case "loading":
//                    console.log("LOADING"+new Date());
//                    break;
//                case "loaded":
//                    console.log("loaded"+new Date());
//                    break;
//                case "interactive":
//                    console.log("interactive"+new Date());
//                    break;
//                case "complete":
//                    console.log("complete"+new Date());
//                    break;
//            }
        });

        console.time("A")

 

A: 5.89208984375ms

B_____Thu May 17 2018 10:23:36 GMT+0800 (CST)

interactive

DOMContentLoadedThu May 17 2018 10:23:36 GMT+0800 (CST)

 B_____Thu May 17 2018 10:23:36 GMT+0800 (CST)

 complete

但是,今天看了:你不知道的 DOMContentLoaded

这里又有疑问:interactive DOMContentLoaded   complete onload三个先后顺序是什么呢?

DOMContentLoaded和interactive:表示文档解析完成,且资源未完全加载完成。区别呢?执行顺序呢?

验证表明:interactive 》DOMContentLoaded 》 complete 》 onload

但是,DOMContentLoaded触发时候,document.readyState一般是interactive,也有可能complete。而当页面有大量的二进制文件(页面加载的时长大于阻塞的时长的时候),document.readyState=complete 可能反而在 onload 事件之后才能触发(这个我未完成验证出这种情况)

我觉得onreadystatechange这个不是很靠谱,一般用DOMContentLoaded判断页面解析完全。希望哪位大牛提供这方面的补充,感激不尽!

在图片上,也有onload跟complete

document.getElementById('load').onclick = function() {  
    var img = new Image();  
    if(img.complete) {  
        console.log('dd');  
    }  
    img.onload = function() {  
        console.log('ff')  
    }  
    img.src="images/1-logo.png";  
  
}

这里顺带提下img加载相关 属性

  • onload:表示加载好,换言之,没有加载好不会执行;

  • onAbort:图片加载的时候,用户通过点击停止加载时出发

  • onerror:如果图片不存在(网络很不通畅,也可能触发 onerror事件)

  • complete:图片显示出来以后为true,

转载请注明来源:再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载

文有不妥之处,请告知,谢谢!

参考文章:

你不知道的 DOMContentLoaded

JS、CSS以及img对DOMContentLoaded事件的影响

浏览器线程阻塞和无阻塞加载脚本的理解

css加载会造成阻塞吗?

 

分享到:
评论

相关推荐

    Javascript封装DOMContentLoaded事件实例

    主要介绍了Javascript封装DOMContentLoaded事件实例,DOMContentLoaded是FF,Opera 9的特有的Event, 当所有DOM解析完以后会触发这个事件,需要的朋友可以参考下

    domloaded检查何时加载DOM类似于DOMContentLoaded

    dom-loaded 检查何时加载DOM类似于`DOMContentLoaded`

    关于HTML5+ API plusready的兼容问题

    3. 触发DOMContentLoaded事件 4. 注入5+ API 5. 触发plusready事件 这样导致5+ API生效时间比较延后,在html中引用js执行之后才能调用5+ API,通常采用以下代码调用5+ API: document.addEventListener('plusready'

    JS兼容所有浏览器的DOMContentLoaded事件

    主要介绍了JS兼容所有浏览器的DOMContentLoaded事件的相关资料,标准浏览器中,使用DOMContentLoaded事件即可实现我们的要求,注册事件处理函数也极为简单,感兴趣的朋友一起学习吧

    WEB页面性能测试专业术语+页面性能测试指标采集方式

    页面性能测试专业术语:白屏时间、首次页面加载完成时间(首屏加载时间)、非首次页面加载完成时间、首屏绘制(First Paint,FP)、首屏内容绘制(First Contentful Paint,FCP)、最大内容绘制(Largest Contentful...

    28种CSS3炫酷loading页面加载动画

    28种CSS3炫酷loading页面加载动画特效代码,兼容主流浏览器 使用方法: 1、在head区域引入样式表文件demo.css和loaders.css 2、在你的网页中加入&lt;!-- 代码 开始 --&gt;&lt;!-- 代码 结束 --&gt;注释区域代码即可...

    28种CSS3炫酷loading页面加载动画特效代码

    28种CSS3炫酷loading页面加载动画特效代码,兼容主流浏览器 使用方法: 1、在head区域引入样式表文件demo.css和loaders.css 2、在你的网页中加入&lt;!-- 代码 开始 --&gt;&lt;!-- 代码 结束 --&gt;注释区域代码即可 3、末尾调用...

    dom-loaded:检查DOM是否已像DOMContentLoaded一样加载

    加载了dom 检查DOM是否已像一样加载与DOMContentLoaded不同,这在加载DOM之后包含在内时也适用。安装$ npm install dom-loaded用法import domLoaded from 'dom-loaded' ;await domLoaded ;console . log ( 'The DOM ...

    littlepjs-文档-加载-nyc-web-080519

    确定“正确时间”的因素很多,但是有两个事件在页面加载方面代表了两个特别重要的里程碑: 从基础html完全解析页面的DOM时,将触发DOMContentLoaded事件资源及其所有从属资源(包括CSS和JavaScript)完成加载后,将...

    littlepjs-文档-加载-nyc01-seng-ft-042020

    资源及其所有从属资源(包括CSS和JavaScript)完成加载后,将触发load事件 在本课程中,我们将重点介绍DOMContentLoaded 。 为什么DOMContentLoaded很重要? DOMContentLoaded事件是浏览器的内置方法,用于指示何时...

    js页面生命周期1

    页面生命周期:DOMContentLoaded,load,beforeunload,unloadHTML 页面的生命周期包含三个重要事件:DOMContentL

    harmonyos2-loader:使用iframe的资源加载器

    使用非阻塞行为并行加载资源 避免延迟load和DOMContentLoaded事件 如果需要,即使在load事件之后也保留脚本执行顺序 基于可重用源代码(工厂字符串)构建持久模块化系统 目前,一个小的loadJSON.js独立助手显示了大...

    JS、CSS以及img对DOMContentLoaded事件的影响

    最近在做性能有关的数据上报,发现了两个非常有意思的东西:Chrome开发者工具的Timeline分析面板,以及DOMContentLoaded事件。一个是强大的令人发指的性能分析工具,一个是重要的性能指标,于是就用Timeline对...

    htmlspitter:NodeJS服务器使用Puppeteer从加载的JS吐出HTML

    HTMLSpitter 带有NodeJS服务器的轻量级Docker映像,可使用Puppeteer和... wait是在停止预渲染之前要等待的可选加载事件。 有可能: load (等待load事件) domcontentloaded (等待DOMContentLoaded事件) netwo

    fewpjs-document-onload-rcdd_202104_tur_few

    资源及其所有从属资源(包括CSS和JavaScript)完成加载后,将触发load事件 在本课程中,我们将重点介绍DOMContentLoaded 。 为什么DOMContentLoaded很重要? DOMContentLoaded事件是浏览器的内置方法,用于指示何时...

    littlepjs-document-onload-dc-web-120919

    资源及其所有从属资源(包括CSS和JavaScript)完成加载后,将触发load事件 在本课程中,我们将重点介绍DOMContentLoaded 。 为什么DOMContentLoaded很重要? DOMContentLoaded事件是浏览器的内置方法,用于指示何时...

    somepjs-document-onload-dc-web-102819

    资源及其所有从属资源(包括CSS和JavaScript)完成加载后,将触发load事件 在本课程中,我们将重点介绍DOMContentLoaded 。 为什么DOMContentLoaded很重要? DOMContentLoaded事件是浏览器的内置方法,用于指示何时...

Global site tag (gtag.js) - Google Analytics