document.createElement('script') 和 DOMContentLoaded 执行顺序

上篇在翻译一篇文章时看到:

脚本不阻塞DOMContentLoaded

此规则有两个特例:

  1. 脚本有 async 属性,我们稍后会提到此属性,不会阻塞 DOMContentLoaded
  2. 脚本由 document.createElement('script') 动态生成,然后加入到html文档,也不会阻塞 DOMContentLoaded

上边提到的第一条规则我之前是知道的: async 脚本会在 load 事件之前发生,但不能保证 DOMContentLoaded 事件先后的执行顺序 ,也就是说不一定阻塞渲染(如果文件过大,下载时间长(浏览器渲染和js文件下载并行执行),有可能在浏览器渲染完成后才下载完,也就是在DOMContentLoaded事件触发后才执行)

那么,第二条规则的我之前没有留意过,有可能跟平时使用的少有关(几乎没有使用过)。我第一时间会质疑:这个说法成立吗?

实践出真知,实例代码如下:

loadScripts([
    './dynamic-script.js',
  ], function () {
    alert('dynamic-script: loaded')
  })

  document.addEventListener('DOMContentLoaded', () => {
    alert('DOMContentLoaded')
  })
  
  window.onload = () => {
    alert('onload')
  }

  function loadScripts(array,callback){
    var loader = function(src,handler){
      var script = document.createElement("script");
      script.src = src;
      script.onload = script.onreadystatechange = function(){
        script.onreadystatechange = script.onload = null;
        handler();
	console.log(script.async) // 注1
      }
      var head = document.getElementsByTagName("head")[0];
      (head || document.body).appendChild( script );
    };
    (function run(){
      if(array.length!=0){
        loader(array.shift(), run);
      }else{
        callback && callback();
      }
    })();
  }
// dynamic-script.js
alert('dynamic-script-content')

执行结果如下:

  1. 打印 'DOMContentLoaded'
  2. 打印 'dynamic-script-content'
  3. 打印 'dynamic-script: loaded'
  4. 打印 true(控制台)
  5. 打印 'onload'

结果跟上边的描述是一致的。。。

网上查阅资料解释:JavaScript异步加载的三种方式——async和defer、动态创建script

在没有定义defer和async之前,异步加载的方式是动态创建script,通过window.onload方法确保页面加载完毕再将script标签插入到DOM中。

MDN解释: <script>

[1] 在不支持该async属性的旧浏览器中,解析器插入的脚本会阻塞解析器;插入脚本的脚本在 IE 和 WebKit 中异步执行,但在 Opera 和 4.0 之前的 Firefox 中同步执行。在 Firefox 4.0 中,asyncDOM 属性默认为true用于脚本创建的脚本,因此默认行为与 IE 和 WebKit 的行为相匹配。要请求在document.createElement("script").async评估为的浏览器true(例如 Firefox 4.0)中以插入顺序执行插入脚本的外部脚本,请.async=false在要维护顺序的脚本上设置。永远不要document.write()从async脚本中调用。在 Gecko 1.9.2 中,调用document.write()具有不可预测的效果。在 Gecko 2.0 中,document.write()从async 脚本无效(除了向错误控制台打印警告)。

此外,还有一篇文章提到: DOMContentLoaded talk about blocking and rendering - analysis and resource loading html page events

为了验证上边的解释,我在实例代码中(// 注1)处对script的async值进行了输出,由 document.createElement("script") 创建的脚本确实是异步的。

记忆技巧

首先我们来看 document.createElement("script") ,这里边有个 document... 也就是说我们需要使用 document 对象,何时才能访问到 document 对象? 答案显而易见:dom树构建完毕时(此时页面还没有生成cssom,render tree,布局,绘制balabala)。。。我们考虑下这段动态插入的脚本肯定不是很重要的(如果很重要,需要在关键渲染路径之前执行,肯定会写为内联脚本),既然这段脚本不是很重要,那就不能影响关键渲染路径的性能,那就等浏览器首屏渲染完成后(这句可能不那么准确)再下载执行也不迟。。。所以设计者就将其放在 DOMContentLoaded 之后下载执行,从而实现异步加载,也就是不阻塞 DOMContentLoaded 事件。

posted @ 2021-06-24 00:21  韩帅  阅读(2864)  评论(0编辑  收藏  举报