再谈DOMContentLoaded和load

再谈DOMContentLoaded和load

最近因为业务出海,网站全球化部署,老板对网页性能格外关注,促使我不得不再次翻阅古籍查询网页性能优化相关的蛛丝马迹,对很多知识做了重新梳理,今天我们就来聊一聊下面两个关键的网页性能指标:

  • DOMContentLoaded
  • load

MDN定义

DOMContentLoaded:当初始的 HTML文档完全加载解析完成 之后,DOMContentLoaded 事件被触发,而无需等待样式表图像子框架的完成加载。

load:load事件在整个页面 完全加载 时触发,包括所有相关资源(如样式表css、图像img、脚本js)。这与DOMContentLoaded不同,后者在页面DOM加载后立即触发,而不等待资源完成加载。(原中译版本太简单,我觉得没说清楚,遂根据原文重新翻译了一遍,如果你觉得还不清楚,可以阅读MDN原文)

定义十分精炼,看完仍是一脸懵。你可能会产生如我一样的疑问:什么是完全加载?何时算解析完成?相关资源具体指哪些?

在解惑之前,我们先了解一些基本概念

HTML解析

HTML网页本质是包含字符串的文本文件,而HTML是一种语法规则,HTML解析就是根据HTML语法规则将HTML文本转换为浏览器可识别的数据结构,在解析的过程中分别形成DOM树🌲、CSS树🌲、Render树🌲。

DOM树🌲 构建

浏览器将HTML文档中的所有 DOM 元素构建成一个DOM树🌲,注意构建过程自上而下,遇到同步js脚本时,构建过程会暂停,直到脚本执行完成后继续。

CSS树🌲 构建

浏览器将文档中的所有 CSS 资源合并,形成CSS对象树🌲。

Render树🌲构建

浏览器将 DOM 树🌲和 CSS树🌲 合并成一棵Render 树🌲,Render 树🌲在合适的时机会被渲染到页面中。

HTML文档解析与DOMContentLoaded触发

  • 在既没有CSS也没有JS的情况下,HTML文档的解析过程为:

1992505511

DOMContentLoaded事件的触发时机为:HTML解析为DOM之后。
  • 有CSS无JS的情况下,HTML文档解析过程为:

1138481954

这里与1.不同的地方在于,渲染树的生成是基于DOM和CSSOM的。但是触发DOMContentLoaded的时间依然是在HTML解析为DOM后,无论此时CSS解析为CSSOM的过程是否完成。
  • 当有JS时,HTML文档解析过程为:

1944852356

有一点要注意的是,在 body 中第一个 script 资源下载完成之前,浏览器会进行首次渲染,将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。

异步脚本、延迟脚本与DOMContentLoaded的关系

sync

为了与异步脚本和延迟脚本进行一个更清晰的对比,在这里先将同步脚本的情况分析一下。

1375052445

如上图所示, HTML 文档被解析时如果遇见(同步)脚本,则停止解析,先去加载脚本,然后执行,执行结束后继续解析 HTML 文档。HTML文档解析完毕后触发DOMContentLoaded。

async

对此,《JavaScript高级程序设计》一书的解释是:带async的脚本一定会在load事件之前执行,可能会在DOMContentLoaded之前或之后执行。

为什么async脚本可能会在DOMContentLoaded之前或之后执行呢?或者说,为什么DOMContentLoaded事件的触发既可能在async脚本执行前、又可能在async脚本执行后呢? 这是因为,async 标签的脚本加载完毕的时间有两种情况:

情况1: HTML 还没有被解析完的时候,async脚本已经加载完了,那么 HTML 停止解析,去执行脚本,脚本执行完毕后触发DOMContentLoaded事件。如下图所示:

953068438

情况2: HTML 解析完了之后,async脚本才加载完,然后再执行脚本,那么在HTML解析完毕、async脚本还没加载完的时候就触发DOMContentLoaded事件。如下图所示:

1217503113

defer

如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。

defer脚本同样包含两种情况:

情况1:HTML还没解析完成时,defer脚本已经加载完毕,那么defer脚本将等待HTML解析完成后再执行。defer脚本执行完毕后触发DOMContentLoaded事件。如下图所示

1613105451

情况2:HTML解析完成时,defer脚本还没加载完毕,那么defer脚本继续加载,加载完成后直接执行,执行完毕后触发DOMContentLoaded事件。如下图所示:

1377376992

注意defer情况2与async情况2的两个图非常相似,区别就在于DOMContentLoaded事件的触发时间点。

对于defer脚本,《JavaScript高级程序设计》一书的说法是:“按照h5规范,两个defer脚本会按照它们出现的先后顺序执行,两个脚本会在DOMContentLoaded之前执行。”这和我们上面的分析一致。然而,该书接下来说,“但事实上,defer脚本不一定会按顺序执行,也不一定会在DOMContentLoaded之前执行。” 这。。。待研究

load触发

根据MDN定义,load事件在整个页面 完全加载 时触发,包括所有相关资源(如样式表css、图像img、脚本js)。

我们以js脚本加载来做一些测试,以下是实验代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>onload测试</title>
</head>
<body>
  <div id="onload"></div>
  <script>
    function loadScript(link, success, error) {
      var scriptEl = document.createElement('script');
      scriptEl.onload = function handleLoad() {
        success && success();
      };
      scriptEl.onerror = function handleError(e) {
        console.error ? console.error(e) : console.log(e);
        error && error(e);
      };
      scriptEl.src = link;
      scriptEl.async = true;
      document.body.appendChild(scriptEl);
    }

    // 实验一 同步改变DOM
    loadScript('https://www.tripfe.cn/assets/built/jquery-3.2.1.min.js?v=73e3b39e65');

    // 实验二 异步加载资源
    // setTimeout(() => {
    //   loadScript('https://www.tripfe.cn/assets/built/jquery-3.2.1.min.js?v=73e3b39e65');
    // }, 0);

    function loadCss(link, success, error) {
      var cssEl = document.createElement('link');
      cssEl.onload = function handleLoad(){
        success && success();
      }
      cssEl.onerror = function handleError(e) {
        console.error ? console.error(e) : console.log(e);
        error && error(e);
      }

      cssEl.href = link;
      cssEl.rel = "stylesheet";
      document.head.appendChild(cssEl);
    }

    // 实验一 同步改变DOM
    loadCss('https://www.tripfe.cn/assets/built/screen.css?v=73e3b39e65');

    // 实验二 异步加载资源
    // setTimeout(() => {
    //     loadCss('https://www.tripfe.cn/assets/built/screen.css?v=73e3b39e65');
    // }, 0);

  </script>
</body>
</html>

实验一的结果如下图,确如MDN所述,onload需要等到所有资源完全加载时触发:

WechatIMG48

实验二的结果如下图,采用异步方式加载资源并不会影响onload事件触发

WechatIMG50

注意,这里的完全加载不仅指文件下载完成,还包含解析执行,若是图片,需要等到图片完全展示。

参考文档:

风清洋

风清洋

保持原动力,迎接每一天

评论