一次加载javascript的思考(defer,async)

最近在作我的项目中登录界面的时候,须要加一我的机验证也就是验证码的功能,和朋友商量再三因为时间有限,便选择引入第三方的验证码。使用过程当中发现,该第三方验证码的实现逻辑基本是先编写指定id的HTML标签,而后加载该第三方的js,由其本身渲染而成,所以借这个机会,再次学习总结一下传统的<script>标签加载 JavaScript 脚本相关知识,主要是defer,async属性。javascript

HTML页面解析过程

为了更好的理解脚本加载的知识,首先简单了解一下HTML页面处理过程:(关于解析,请看下文script的内容)html

  1. 浏览器经过HTTP协议请求服务器,获取HMTL文档并开始从上到下解析,构建DOM;
  2. 在构建DOM过程当中,若是遇到外联的样式声明和脚本声明,则暂停文档渲染,建立新的网络链接,并开始下载样式文件和脚本文件;
  3. 样式文件下载完成后,构建CSSDOM;脚本文件下载完成后,解释并执行,而后继续构建DOM
  4. 完成文档解析后,将DOM和CSSDOM进行关联和映射,最后将视图渲染到浏览器窗口

在这个过程当中,普通脚本文件的下载和执行会阻塞文档的渲染,若是控制得很差,在用户体验上就会形成必定程度的影响
所以,大多提倡将脚本加载放在页面末尾,通常是</body>处:前端

雅虎军规第18条:把脚本放在底部

脚本加载实例:java

<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
  // module code
</script>

<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/my.js">
</script>

普通脚本

没有 defer 或 async修饰,浏览器会当即加载并执行指定的脚本,“当即”指的是在渲染该 script 标签后面的文档元素以前,也就是说不等待后续载入的文档元素,读到就加载并执行。
若是解析遇到多个<script>标签,依次加载顺序执行。
*此操做会阻止后续文档元素的解析和渲染,可是这里有一个预解析的概念
(Webkit 和 Firefox 都进行了这项优化。在执行脚本时,其余线程会解析文档的其他部分,找出并加载须要经过网络加载的其余资源。经过这种方式,资源能够在并行链接上加载,从而提升整体速度。请注意,预解析器不会修改 DOM 树,而是将这项工做交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。)*segmentfault

defer脚本

若是script标签设置了该属性,则浏览器会异步的下载该文件而且不会影响到后续DOM的渲染;
若是有多个设置了defer的script标签存在,则会按照顺序执行全部的script;
defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行。
注意:浏览器

*在现实当中,延迟脚本并不必定会按照顺序执行,也不必定会在 DOMContentLoaded事件触发前执行,所以最好 只包含一个延迟脚本。 泽卡斯(Zakas. Nicholas C.). JavaScript高级程序设计(第3版) (图灵程序设计丛书)

async脚本

async的设置,会使得script脚本异步的加载并在容许的状况下执行,也就是说加载和渲染后续文档元素的过程将和script加载并行进行
async的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行。服务器

上述三种总结为一张图片(出处见参考)

bVcQV0

关于项目中的应用

因为是模块化开发,在此采用的是再模块内经过动态方式加载第三方的验证码js,主要代码以下。网络

function load (el, src, callback) {
  if (!src) {
    return;
  }
  // _verifyExist(src);
  let scriptHeat = document.createElement('script');
  scriptHeat.type = 'text/javascript';
  scriptHeat.src = src;
  scriptHeat.defer = true;
  /* 为保证兼容性,在此对回调包装, */
  isFunction(callback) && addOnloadHandler(scriptHeat, callback);
  el.appendChild(scriptHeat);
}
function isFunction (fn) {
  return Object.prototype.toString.call(fn) === '[object Function]';
}
function addOnloadHandler (el, callback) {
  el.onload = el.onreadystatechange = function () {
    if (!this.readyState || // 这是FF的判断语句,由于ff下没有readyState这人值,IE的readyState确定有值
      this.readyState === 'loaded' || this.readyState === 'complete' // 这是IE的判断语句
    ) {
      callback();
    }
  };
}

为保证该第三方库执行时有其渲染的元素,因此设置为defer。可能引起问题是若是网络慢或其余缘由会致使该验证控件呈现较慢(暂时未遇到),因此项目中也加了遮罩处理。app

参考
defer和async的区别
详解defer和async的原理及应用
MDN
前端文摘:深刻解析浏览器的幕后工做原理异步

相关文章
相关标签/搜索