从极迷处识迷,则到处醒;将难放怀一放,则万境宽。——《小窗幽记·集醒篇》
译:在最易令人迷惑的地方识破迷惑,那么无处不是清醒的状态;将最难以放下心怀的事放下,那么到处都是宽广的路。
开始
将Javascript插入HTML的主要方法是使用script元素,
外部引入——通过src属性指定外部JavaScript文件链接
内部嵌入——直接在HTML文档内使用<script></script>标签
对引入
<script>标签包含了两个特殊的属性:defer与async,
async:属性可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本有效。
defer:属性可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。在IE7及更早的版本中,对行内脚本也可以指定这个属性。
——JavaScript高级程序设计
如果没有 defer 和 async 属性,浏览器会立即加载并执行相应的外部脚本,“立即”指的是在渲染该 script 标签之下的(下面的、下文的、紧接着的、紧跟着的、后续的)文档元素之前,也就是说不等待载入后续的文档元素,读到脚本就加载并执行。这样就阻塞了后续文档的加载。
推迟执行脚本 defer(延迟、推迟)
HTML 4.01 为<script>元素定义了一个叫 defer 的属性。这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素上
设置 defer 属性,相当于告诉浏览器立即下载,但延迟执行。
<!DOCTYPE html> <html> <head> <title>Example HTML Page</title> <script defer src="example1.js"></script> <script defer src="example2.js"></script> </head> <body> <!-- 这里是页面内容 --> </body> </html>
虽然这个例子中的<script>元素包含在页面的<head>中,但它们会在浏览器解析到结束的</html>标签后才会执行。
HTML5 规范要求脚本应该按照它们出现的顺序执行,因此第一个推迟的脚本会在第二个推迟的脚本之前执行,而且两者都会在 DOMContentLoaded 事件之前执行。
不过在实际当中,推迟执行的脚本不一定总会按顺序执行或者在 DOMContentLoaded事件之前执行,因此最好只包含一个这样的脚本。
如前所述,defer 属性只对外部脚本文件才有效。这是 HTML5 中明确规定的,因此支持 HTML5的浏览器会忽略行内脚本的 defer 属性。IE4~7 展示出的都是旧的行为,IE8 及更高版本则支持 HTML5定义的行为。
对 defer 属性的支持是从 IE4、Firefox 3.5、Safari 5 和 Chrome 7 开始的。其他所有浏览器则会忽略这个属性,按照通常的做法来处理脚本。考虑到这一点,还是把要推迟执行的脚本放在页面底部比较好。
异步执行脚本 async(异步)
HTML5 为<script>元素定义了 async 属性。从改变脚本处理方式上看,async 属性与 defer 类似。
当然,它们两者也都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与 defer 不同的是,标记为 async 的脚本并不保证能按照它们出现的次序执行,比如:
<!DOCTYPE html> <html> <head> <title>Example HTML Page</title> <script async src="example1.js"></script> <script async src="example2.js"></script> </head> <body> <!-- 这里是页面内容 --> </body> </html>
在这个例子中,第二个脚本可能先于第一个脚本执行。因此,重点在于它们之间没有依赖关系。给
脚本添加 async 属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。正因为如此,异步脚本不应该在加载期间修改 DOM。
异步脚本保证会在页面的 load 事件前执行,但可能会在 DOMContentLoaded之前或之后。Firefox 3.6、Safari 5 和 Chrome 7 支持异步脚本。使用 async 也会告诉页面你不会使用document.write,不过好的 Web 开发实践根本就不推荐使用这个方法(document.write)。
load和DOMContentLoaded事件
- load
MDN的解释:load 应该仅用于检测一个完全加载的页面,当一个资源及其依赖资源已完成加载时,将触发load事件。
意思是页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件。
- DOMContentLoaded
MDN 的解释:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。
不同属性的时间流程图
下面的图片可以看出三者之间的区别:
其中蓝色代表js脚本网络加载时间,红色代表 js 脚本执行时间,绿色代表 html 解析。
紫色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。
也就是说async可能乱序执行,而defer是顺序执行,这也就决定了async比较适用于百度分析或者谷歌分析这类不依赖其他脚本的库,且defer在页面加载完成后才执行,可以在脚本中操作DOM。
从图中可以看到一个普通的<script>标签的加载和解析都是同步的,会阻塞DOM的渲染,
这也是我们经常会把<script>写在<body>底部的原因之一,
为了防止加载资源而导致的长时间的白屏,
另一个原因是js可能会进行DOM操作,所以要在DOM全部渲染完后再执行。
最稳妥的办法还是把<script>
写在<body>
底部,没有兼容性问题,没有白屏问题,没有执行顺序问题,高枕无忧。
细究
async两种情形
async使用场景
主要是不涉及操作DOM的事件,比如使用百度、谷歌分析的脚本
情况1(在 DOMContentLoaded之前执行):HTML 还没有被解析完的时候,async脚本已经加载完了,那么 HTML停止解析,去执行脚本,脚本执行完毕后触发DOMContentLoaded事件。
情况2(在 DOMContentLoaded之后执行): HTML 解析完了之后,async脚本才加载完,然后再执行脚本,那么在HTML解析完毕、async脚本还没加载完的时候就触发DOMContentLoaded事件。
综述:
- 无论html是否解析完成,立即执行脚本;
- 无论有使用多少个async加载脚本,只要脚本下载完成,立即执行脚本。与<script>标签的顺序无关。
defer两种情形
使用场景
操作DOM的脚本,为防止元素尚未加载完成,脚本找不到元素报错。
情况1:HTML还没解析完成时,defer脚本已经加载完毕,那么defer脚本将等待HTML解析完成后再执行。defer脚本执行完毕后触发DOMContentLoaded事件。
情况2: HTML解析完成时,defer脚本还没加载完毕,那么defer脚本继续加载,加载完成后直接执行,执行完毕后触发DOMContentLoaded事件。
综述
- 无论js文件是否下载完成,只有html解析完毕,才可以执行脚本;
- 脚本执行的顺序与下载的完成时间无关,按照<script>脚本的位置,顺序执行
回答面试官
“在解析HTML文档过程中,defer和async标注的脚本都会立即下载。”
“不同的是,defer脚本即使下载完成,也会被延迟到整个页面都解析完毕后再运行,运行结束后触发DOMContentLoaded事件。”
“而async脚本下载完成后立即执行,可能会在 DOMContentLoaded之前或之后,保证在页面的 load 事件前执行。”
加分——
“多个带有async
属性的脚本标签之间的加载顺序是不确定的,哪个脚本先下载完成就先执行哪个。”
“- 多个带有 defer
属性的脚本标签之间的执行顺序是按照它们在文档中出现的顺序来执行的。”
完结
over,撒花✿✿ヽ(°▽°)ノ✿,喜欢的朋友点个赞,欢迎留下你的评论。