请各位读者添加一下作者的微信公众号,以后有新的文章,将在微信公众号直接推送给各位,非常感谢。
0.前言
估计所有读者也慢慢适应我每次讲东西之前先墨迹一段的习惯了。
那么我今天就来个转换,我!不!墨!迹!了!
正文开始。
1.script标签的默认行为
首先我们先来看一下 <script>
标签 的几个重要特性:
- script标签的会阻止文档渲染。相关脚本会立即下载并执行。
注意:(不带defer
或async
属性) -
document.currentScript
可以获得当前正在运行的脚本(Chrome 29+, FF4+)。 - 脚本顺序再默认情况下和script标签出现的顺序一致。
既然我们已经明确了 script 标签的基本特性。
那我们就先来看一个例子,用这个例子来说明 script 的加载顺序问题。
2. script 的加载顺序
假设如下简单代码,我们去看一下,执行的结果是什么样子?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>李鹏:3206064928</title>
</head>
<body>
</body>
<script>alert("MR_LP : A");</script>
<script>alert("MR_LP : B");</script>
<script>alert("MR_LP : C");</script>
</html>
执行结果如下:
这是我们按照正常的逻辑去执行,分别弹出了 :
- MR_LP : A
- MR_LP : B
- MR_LP : C
那我们再去考虑一下,在有网络的情况,请求的顺序会怎样呢?
在这里我去做一个限制,将我们的第二个文件中,书写非常多的注释,让我们的第二个文件大小要远远大于另外两个文件。
这时候我们再去看一下,顺序是什么样子的。
结果如何呢?
你会发现我们的加载顺序和上面一模一样。
这是为什么呢?第二个文件明明要大于另外两个文件呀。
这里就要说到 script标签 的柱塞式执行方式了。
3. script 标签的属性
在说上面的问题之前,咱们先回顾一下,script 标签 的六个属性。
|属性名|介绍|
:--|:--|:--|:--
1|type|可以看成language的替代属性;表示编写代码使用的脚本语言的内容类型。默认值为text/javascript。|必须
2|src|表示包含要执行代码的外部文件。|可选
3|charset|属性与 src 属性一起使用,告诉浏览器用来编码这个 JavaScript 程序的字符集。它的值是任何一个 ISO 标准字符集编码的名称。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。|可选
4|language|原来用于表示编写代码用的脚本语言,因大多数浏览器都会忽略这个属性,因此也没必要再用了。|已废弃
5|async|表示立即下载该脚本,但不妨碍页面中的其他操作(比如:下载其他资源或等待加载其他脚本),只对外部文件有效。|可选
6|defer|表示脚本可以延迟到文档完全被解析和显示后再执行。只对外部文件有效。|可选
注意:
虽然text/javascript 和text/ecmascript 都已经不被推荐使用,
但人们一直以来使用的都还是text/javascript。
实际上,服务端在传送JavaScript 文件时使用的 MIME 类型通常是application/x-javascript,
但在type中设置这个值却可能导致脚本被忽略。
另外,在非IE浏览器中
还可以使用以下值:application/javascript 和application/ecmascript。
考虑到约定成俗和最大限度的浏览器兼容性,
目前type 属性的值依旧还是text/javascript。
除此之外,属性 1 ~ 4 是 HTML4.01中定义的,而 5 和 6 则是 HTML 5 中新增的内容,那我们接下来就专门针对这两个新属性,来看一下,script 的柱塞式执行。
4.async属性
async属性是HTML5的新特性,这意味着其兼容性并不乐观(IE10+)。
async表示该script标签并不柱塞,也不同步执行。
浏览器只需要在脚本下载完毕后再执行即可——不必柱塞页面渲染等待该脚本的下载和执行。
如下代码[^4],会得到三个alert,但是alert的内容分别是"A","C","B"。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>李鹏:3206064928</title>
</head>
<body></body>
<script src="https://snipt.net/raw/7b08744009c450e07c0bfc1d606fc72e/"></script>
<script src="https://snipt.net/raw/a2e8c05c1f6fc0e47d259aa899304e89/" async="async"></script>
<script src="https://snipt.net/raw/4fab3017d3d46cbfc4bbd88aab006650/"></script>
</html>
运行结果如下:
可以看到,第二个script标签在加入async
后,并没有阻止后续文档解析和脚本执行。
考究这个属性产生的原有,其实有大量的脚本加载器在做这样的事情:
var script = document.createElement("script");
script.src = "file.js";
document.body.appendChild(script);
那么我们不难想象,通过脚本异步插入的script标签达到的效果和带async属性的script标签是一样的。
换句话说,由脚本插入的script标签默认是async的。
另外,对內联脚本设置async属性是没有意义的,也不产生其他效果。
其包含的脚本总是立即执行的。
5. defer属性
带有defer属性的脚本,同样会推迟脚本的执行,并且不会阻止文档解析。
就如同这个脚本,放置到了文档的末尾(</body>
之前)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>李鹏:3206064928</title>
</head>
<body></body>
<script src="https://snipt.net/raw/7b08744009c450e07c0bfc1d606fc72e/"></script>
<script src="https://snipt.net/raw/a2e8c05c1f6fc0e47d259aa899304e89/" defer="defer"></script>
<script src="https://snipt.net/raw/4fab3017d3d46cbfc4bbd88aab006650/"></script>
</html>
上面的代码的宏观现象和加了async属性的例子是一样的,都是会得到"A"、"C"、"B"的三个alert。
但是其原理是不一样的。
defer属性是会确保脚本在文档解析完毕后执行的。
即使这个脚本在文档解析过程中就已经下载完毕变成可执行的状态,
浏览器也会推迟这个脚本的执行,直到文档解析完毕,并在DOMContentLoaded之前。
同时,带有defer的脚本彼此之间,能保证其执行顺序。
注意,defer属性并不是每个浏览器支持,
即便支持的浏览器,也会因为版本不一样导致具体行为不一致。
另外,大家可以通过将script标签放置到文档末尾这种简单的做法达到defer属性一样的效果。
defer属性早在IE4就被支持,但是这个defer属性和现代浏览器的行为是有区别的。
只有IE10以上,才开始按照标准执行defer属性。
6. async与defer的影响
参考W3C的官方文档,defer和async两个属性是可以互相影响的:
There are three possible modes that can be selected using these attributes. If the async attribute is present, then the script will be executed asynchronously, as soon as it is available. If the async attribute is not present but the defer attribute is present, then the script is executed when the page has finished parsing. If neither attribute is present, then the script is fetched and executed immediately, before the user agent continues parsing the page.
简单的归纳:
- 仅有async属性,脚本会异步执行
- 仅有defer属性,脚本会在文档解析完毕后执行
- 两个属性都没有,脚本会被同步下载并执行,期间会柱塞文档解析
规范里没有提到两种属性都有时的效果,但这是文档中被允许的。
这样的具体效果会在后面讨论。
7. document.write的影响
docuemnt.write允许向打开的文档流中写入文档内容;
内嵌到HTML里面的docuemnt.write可以就地添加文档内容。
考虑到docuemnt.write写入script标签的情况:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>李鹏:3206064928</title>
</head>
<body></body>
<script src="https://snipt.net/raw/7b08744009c450e07c0bfc1d606fc72e/"></script>
<script>document.write("\<script src=https://snipt.net/raw/a2e8c05c1f6fc0e47d259aa899304e89 \/\>\<\/script\>");</script>
<script src="https://snipt.net/raw/4fab3017d3d46cbfc4bbd88aab006650/"></script>
</html>
执行结果如下:
我们观察到执行顺序和普通的script标签没有区别。
即使你插入的标签带有async或defer,其行为也是没有区别的。
让人纠结的是反过来使用。
将我们的 document.write 写在页面中时,其余加载按照正常顺序。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>李鹏:3206064928</title>
</head>
<body></body>
<script src="https://snipt.net/raw/7b08744009c450e07c0bfc1d606fc72e/"></script>
<script src="https://snipt.net/raw/773d794c9051a238e5c102e65dbaa2e9/"></script>
<script src="https://snipt.net/raw/4fab3017d3d46cbfc4bbd88aab006650/"></script>
</html>
加载结果如下:
由于第二个脚本是通过document.write
写入的。
被延迟的脚本在执行时,document已经关闭,document.write
是没有任何效果的。
所以,不管使用defer还是async,第二个脚本始终没有运行。
本文参考于 《JS 编程指南》
网友评论