JavaScript 是如何工作的系列——第一篇
前言
我们都知道在默认情况下,JavaScript 的下载和执行会阻塞 HTML 的解析,结果会导致从打开页面到显示出网页内容的过程耗时较长,用户体验不好。本篇文章主要介绍了 JavaScript 的下载和执行是如何阻塞 HTML 解析的 ,以及如何避免阻塞。
JavaScript 的下载和执行
我们以下面这段代码为例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script src='./js/index1.js'></script>
<script src='./js/index2.js'></script>
<script src='./js/index3.js'></script>
</head>
<body>
<div>Downloading Scripts to Execution</div>
</body>
</html>
index1.js 文件内容:
console.log('index1');
index2.js 文件内容:
console.log('index2');
index3.js 文件内容:
console.log('index3);
当我们在浏览器中打开上述 HTML 文件之后,浏览器就会开始解析 HTML 代码。当浏览器遇到 <head> 标签中的第一个 <script> 标签(index1.js)后,HTML 的解析会暂停。此时浏览器会发送一个 HTTP 请求去下载 index1.js 文件,当脚本下载完成,浏览器开始解析并解释执行下载的脚本( index1.js)。当 index1.js 执行完毕,浏览器继续解析 HTML 代码。当遇到第二个 <script> 标签(index2.js)后,处理过程同上,后面依次类推。
由此可见,脚本的数量与消耗的时间是成正比的。在<head> 标签中引入的脚本越多,用户等待的时间越长。
避免阻塞方式
那么有什么办法可以解决上述问题呢?
最常见的方式就是将 <script> 标签放到 HTML 文档最后,</body>标签之前。这会提高网页加载速度,因为 HTML 加载不再受制于脚本加载,当 HTML 内容解析完毕后,才会开始加载脚本。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<div>Downloading Scripts to Execution</div>
<script src='./js/index1.js'></script>
<script src='./js/index2.js'></script>
<script src='./js/index3.js'></script>
</body>
</html>
当然还有两种方式可以解决上述问题:(只适用于外部脚本)
- defer 属性
- async 属性(HTML5)
defer
使用 defer 属性后:
- JavaScript 脚本下载在新的线程中进行,不会阻塞 HTML 解析;
- 脚本下载完成后,不立即执行,在 HTML 解析完成后按照顺序执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script defer src='./js/index1.js'></script>
<script defer src='./js/index2.js'></script>
<script src='./js/index3.js'></script>
</head>
<body>
<div>Downloading Scripts to Execution</div>
</body>
</html>
async
使用 async 属性后:
- JavaScript 脚本下载在新的线程中进行,不会阻塞 HTML 解析;(同 defer)
- 脚本下载完成后,立即解析,此时会阻塞 HTML 的解析。解析完毕后,在另一个线程中解释执行,此时不会阻塞 HTML 的解析。(多个脚本的执行顺序无法预测)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script async src='./js/index1.js'></script>
<script async src='./js/index2.js'></script>
<script src='./js/index3.js'></script>
</head>
<body>
<div>Downloading Scripts to Execution</div>
</body>
</html>
从上图中可以看到,JavaScript 脚本的执行顺序是:index1.js -> index3.js -> index2.js,这可能与你预想的执行顺序不同。这是因为使用 async 属性的脚本,其执行顺序与文件大小、下载速度、解析速度等息息相关。
总结
本文示例代码已上传到 github,感兴趣的读者可以自行下载。本地运行后,观察使用不同的方式引入 JavaScript 脚本后,页面的加载速度。
下一篇
本篇文章主要介绍了浏览器是何时开始下载和执行 JavaScript 的 ,以及阻塞 HTML 解析问题。下篇文章将深入 JS 引擎,了解 JavaScript 代码执行的具体流程。
参考:
The Journey of JavaScript: from Downloading Scripts to Execution - Part I
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。