上一篇刚转载了一篇有关于网站性能优化的文章,其中提及到了页面的加载和渲染的过程,提到了defer和async的相关区别,但是本人在此之前并没有深究其中的区别。

defer和async是script标签的两个属性,用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。
在介绍他们之前,我们有必要先了解一下页面的加载和渲染过程:

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

在这个过程中,脚本文件的下载和执行是与文档解析同步进行,也就是说,它会阻塞文档的解析,如果控制得不好,在用户体验上就会造成一定程度的影响。

所以我们需要清楚的了解和使用defer和async来控制外部脚本的执行。

先来简明概要的介绍一下下面三者的区别:

  1. <script src="script.js"></script>
    没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
  2. <script async src="script.js"></script>
    有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)
  3. <script defer src="myscript.js"></script>
    有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

接着,我们来看一张图:

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

此图告诉我们以下几个要点:

  1. defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  2. 它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的
  3. 关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用
  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
  5. 仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

下面为了演示脚本的执行情况,借鉴了详解defer和async原理及应用


为了演示脚本的执行情况,进而介绍这两个属性的作用,我们先来搭建一个简单的服务器:
创建了一个简易的Node服务器server.js,其代码如下:

var http = require('http');
var fs = require('fs');var typeMapping = {'html': 'text/html','js'  : 'text/javascript','css' : 'text/css','ico' : 'image/x-icon'
};var getResourceExtension = function(req) {var url = req.url;var lastIndexOfDot = url.lastIndexOf('.');if (lastIndexOfDot === -1) return 'text/plain';return url.substring(lastIndexOfDot + 1);
};var respondResourceToClient = function(req, res) {//read the reource and respond via 'pipe'fs.createReadStream(req.url.replace(/^\//, '')).pipe(res);
};var server = http.createServer(function(req, res) {console.log('requesting url: ', req.url);var extension = getResourceExtension(req);res.writeHead(200, {'Content-Type': typeMapping[extension]});var delay = function(time) {setTimeout(function() {respondResourceToClient(req, res);}, time || 0);};if (extension === 'html' || extension === 'css') {delay(0);} else if (extension === 'js') {delay(1000);} else {res.end('');}
});server.listen(3000);console.log('listening at port 3000...');

从上面的代码我们可以看出,当服务器接收到请求之后,会判断请求资源是否为JS,如果是则延迟1s后返回对应的资源。
启动这个服务很简单,只需执行node server.js即可,然后就可以在浏览器中输入http://localhost:3000/app/index.html访问主页了
现在我们来看看index.html中的内容:

<!DOCTYPE html>
<html><head><title>defer & async</title><link rel="stylesheet" type="text/css" href="css/main.css"><script type="text/javascript" src="js/1.js"></script></head><body><div class="text">Hello World</div><script type="text/javascript" src="js/2.js"></script></body>
</html>

在这个HTML文档中,我们先在head中引用了一个外部的脚本文件js/1.js,然后在我们要显示的Hello World后面又引用了一个js/2.js,它们的内容都很简单,就是弹出对应的标示信息:

// js/1.js
alert(1);// js/2.js
alert(2);

下面我们就来访问主页,看看会发生些什么:

从图中可以看到,渲染的过程的确是自上而下,同步进行的,也就是说遇到外部的脚本,就得暂停文档的解析,下载并且解释执行,这种方式是阻塞的,会造成网页空白的现象。

现在稍微修改一下代码,将head中的script标签加上defer属性,然后也稍微改动一下两个JS文件:

<!DOCTYPE html>
<html><head><title>defer & async</title><link rel="stylesheet" type="text/css" href="css/main.css"><!-- adding a 'defer' attribute, by default, the value will be 'true' --><script type="text/javascript" src="js/1.js" defer></script></head><body><div class="text">Hello World</div><script type="text/javascript" src="js/2.js"></script></body>
</html>
// js/1.js
console.log(1);// js/2.js
console.log(2);

再次访问index.html,我们会在控制台中看到下面的执行顺序:

显而易见,1.js被延后致至文档解析完成后执行了,它的执行顺序比body中的

<!DOCTYPE html>
<html><head><title>defer & async</title><link rel="stylesheet" type="text/css" href="css/main.css"><!-- adding a 'defer' attribute, by default, the value will be 'true' --><script type="text/javascript" src="js/1.js" defer></script><script type="text/javascript" src="js/2.js" defer></script><script type="text/javascript" defer>console.log(3);</script></head><body><div class="text">Hello World</div><script type="text/javascript">document.addEventListener("DOMContentLoaded", function() {console.log('dom content loaded, ready state:', this.readyState);}, false);window.addEventListener('load', function() {console.log('window loaded, dom ready state:', document.readyState);}, false);</script></body>
</html>

可以看到,外联的脚本是按照声明顺序执行的,内联脚本并没有遵守这个规则,另外,DOMContentLoaded和load事件依次被捕获,DOM的状态分别变为interactive和complete

接下来我们介绍一下async属性,为了能够很好的演示执行顺序,我们还需要一些修改:

<!DOCTYPE html>
<html><head><title>defer & async</title><link rel="stylesheet" type="text/css" href="css/main.css"><!-- adding a 'async' attribute, by default, the value is 'true' as well --><script type="text/javascript" src="js/1.js" async></script><script type="text/javascript" src="js/2.js" async></script><script type="text/javascript" src="js/3.js" async></script></head><body><div class="text">Hello World</div></body>
</html>

JS文件内如下:

// js/1.js
console.log(1);// js/2.js
console.log(2);// js/3.js
console.log(3);

再次访问index.html,会发现控制台打印如下:

我们发现,3个脚本的执行是没有顺序的,我们也无法预测每个脚本的下载和执行的时间和顺序。async和defer一样,不会阻塞当前文档的解析,它会异步地下载脚本,但和defer不同的是,async会在脚本下载完成后立即执行,如果项目中脚本之间存在依赖关系,不推荐使用async。

关于async,也需要注意以下几点:
1. 只适用于外联脚本,这一点和defer一致
2. 如果有多个声明了async的脚本,其下载和执行也是异步的,不能确保彼此的先后顺序
3. async会在load事件之前执行,但并不能确保与DOMContentLoaded的执行先后顺序

以上就是defer和async的介绍,了解和掌握这两个属性的作用,不仅对JS代码执行的控制更加熟练,也会对页面渲染多一分了解。

参考文章:

  • http://blog.csdn.net/liuhe688/article/details/51247484
  • https://segmentfault.com/q/1010000000640869

defer和async的原理与区别相关推荐

  1. script的defer和async

    我们常用的script标签,有两个和性能.js文件下载执行相关的属性:defer和async defer的含义[摘自https://developer.mozilla.org/En/HTML/Elem ...

  2. script标签中defer和async的区别

    defer 和 async属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面的解析,其区别如下: defer为true:延迟加载脚本,在文档完成解析完成开始执行,并且在DOMContentLoa ...

  3. JS中script标签defer和async属性的区别

    向html页面中插入javascript代码的主要方法就是通过script标签.其中包括两种形式,第一种直接在script标签之间插入js代码,第二种即是通过src属性引入外部js文件.由于解释器在解 ...

  4. JavaScript 的 defer 与 async

    当解析器遇到 script 标签时,文档的解析将停止,并立即下载并执行脚本,脚本执行完毕后将继续解析文档.但是我们可以将脚本标记为 defer,这样就不会停止文档解析,等到文档解析完成才执行脚本,也可 ...

  5. script标签中的defer和async属性

    默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染.如果是外部脚本,还必须加入脚本下载的时间. 如果脚本 ...

  6. html5 js阻塞加载,js无阻塞加载和defer、async详解_白峰_前端开发者

    无阻塞加载 把 下载是异步的没问题,但是每个javascript执行的时候还是同步的,就是先出现的script标签一定是先执行,即使是并行下载它是最后一个下载完成的,除非标有defer的script标 ...

  7. html5中defer的属性,HTML5中defer和async的比较

    在网站页面中,通常需要引入外部js资源,然而外部的js资源可能导致DOM阻塞,影响页面加载速度.通过异步或者延迟执行js,可以做到引用外部js资源而不阻塞DOM的目的.用法是直接在script标签中使 ...

  8. script 标签上的 defer 和 async 属性是什么?

    我们经常使用 script 标签向页面插入一个常规的 JavaScript 文件: <script src="/path/to/script.js"></scri ...

  9. async await 原理

    一.async await具体使用规则 (1)async的函数在执行后都会自动返回一个Promise对象,有无值根据有无return值. (2)await必须在async函数里使用,不能单独使用. ( ...

最新文章

  1. Rinne Loves Data Structure
  2. 说实话你现在有多少存款?清华北大毕业生晒出了自己的收入
  3. 2017-2018-2 20179209《网络攻防》第六周作业
  4. yii2 modal弹窗之ActiveForm ajax表单异步验证
  5. Machine Learning On Spark——基础数据结构(二)
  6. cvs配电保护断路器_电工电器(三)-配电电器-断路器类-剩余电流保护断路器
  7. 浅析MySQL存储引擎序列属性
  8. cpp中vector动态数组(一种container)的简单用法
  9. 全国python一级考试_全国青少年软件编程(python)等级考试试卷(一级)
  10. Java集合里的一些“坑”
  11. Bootstrap (remote)事件监听多次
  12. mysql卸载后重装不_mysql卸载无法重装怎么办?
  13. matlab锁相放大器,锁相放大器原理和模块实现与仿真.DOC
  14. EA使用小技巧-控制图面拷贝时的边框
  15. 2017年第八届CSTQB®国际软件测试高峰论坛议题征集启动
  16. Markdown编辑器简单大概语法学习
  17. 晶体二极管的主要参数
  18. 我是个程序员,每天敲敲打打,哪天电脑崩溃了会发现我这辈子啥都没留下
  19. 不礼让行人怎么抓拍的_斑马线前不礼让行人抓拍设备增加!详细图解告诉你各情况下怎么让才对!...
  20. 如何在Abaqus中用扫掠的方法画六面体网格

热门文章

  1. Excel表Ctrl+v和Ctrl shift+v有什么区别_Ctrl键与10个数字键,26个字母键的组合应用技巧解读...
  2. php 获取文件大小 修改时建,php遍历目录输出文件大小,类型,修改时间.
  3. win10树莓派改ip_Window 10通过网线和Wifi连接树莓派
  4. java在虚拟机下的安装_centos 虚拟机下安装 Java方法
  5. 四线接近开关接线图_开关、电机、断路器、电热偶、电表接线图!非常齐全,快囤起来吧...
  6. xnio java_java基础篇---新I/O技术(NIO)
  7. php与mysql列表_PHP+Mysql+jQuery实现的查询和列表框选择
  8. 全文搜索引擎 ElasticSearch 还是 Solr?
  9. 【计算机算法设计与分析】——排序
  10. PAT(乙级)1009