目录
前言
对于大多数的开发者来说,JavaScript在浏览器中的性能问题,可以认为是开发者所面临的最严重的可用性问题。Javascript执行过程耗时越久,浏览器等待响应的时间就越长(白屏时间较长,导致用户体验不好)。
在页面加载的过程中,<script>标签每次出现都会霸道地让页面等待脚本的额解析和执行,无论当前的JavaScript代码是内嵌的还是包含在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成。在这个过程中,页面渲染和用户交互完全是被阻塞的。
1. 脚本位置
在HTML4规范汇总指出
<script>
标签可以放在<head>
或<body>
中,并允许出现多次。理论上来说,把与样式和行为有关的脚本放在一起,并一起加载他们有助于确保页面渲染和交互的正确性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="./file1.js"></script>
<script src="./file2.js"></script>
<title>Document</title>
</head>
<body>
<h1>hello123213</h1>
</body>
</html>
以上看似正常的代码实际上有十分严重的性能问题:在<head>
中加载了2个Javascript
文件,由于脚本会阻塞页面的渲染,直到它们全部下载并执行完成后,页面的渲染才会继续,因此页面的性能问题会很明显。
我们可以看到,只有当所依赖的JavaScript文件全部加载执行完成之后,才会继续解析渲染页面,这样会造成页面的白屏时间较长,导致用户体验很差。
通过上图我们可以发现,第一个JavaScript文件下载时,同时也阻塞了其他文件的下载。从file1下载完成到file2开始下载有一段延时,这段时间正好时file1执行的过程。
由于脚本会阻塞页面其他资源的下载,因此我们推荐将<script>
标签放到<body>
的底部,以减少对整个页面下载的影响。
我们再看看这段代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>hello123213</h1>
<script src="./file1.js"></script>
<script src="./file2.js"></script>
</body>
</html>
可以看一下效果
将<script>
标签放到body
底部时,尽管用户体验有明显提升,但是一个脚本的执行还是会阻塞另一个脚本。所以优化Javascript的首要规则就是将脚本放在底部
2. 组织脚本
由于每个<script>
标签初始下载时都会阻塞页面大的渲染,所以减少页面包含的<script>
标签数量有助于改善这一情况。 浏览器在解析HTML页面的过程中每遇到一个<script>
标签,都会因执行脚本而导致一定的延时,所以最小化延迟时间将会明显改善页面的总体性能。
考虑到HTTP请求会带来额外的性能开销,因此下载单个100KB的文件将比下载4个25KB的文件更快。
3. 延迟的脚本
Defer:HTML4为<script>标签定义了一个扩展属性,Defer属性指明本元素所含的脚本不会修改DOM,因此代码能安全地延迟执行。带有defer属性的<script>
标签可以放置在文档的任何位置。JavaScript文件在解析到<script>
标签时开始下载,但不会执行,知道DOM加载完成(onload事件被触发前)。当一个带有defer属性的Javas文件下载时,他不会阻塞页面浏览器的其他进程,因此这类文件可以与页面中的其他资源并行下载。
async:HTML5规范中引入了async属性,用于异步加载脚本。async与defer的相同点是采用并行下载,在下载过程中不会产生阻塞。区别在于执行时机,async是在加载完成之后自动执行,而defer需要等待页面完成后执行。
小结
JavaScript代码在执行过程中会阻塞浏览器的其他进程,比如用户绘制界面,每次遇到<script>
标签页面都必须停下来等待代码下载并执行,然后处理其他部分。尽管如此,还是有几种方法能减少Javascript对性能的影响:
-
</body>
闭合标签之前,将所有的<script>
标签放在页面底部。这能确保在脚本执行前页面已经完成了渲染。 -
合并脚本:页面中的
<script>
标签越少,加载也就越快,响应也更迅速。 -
使用
<script>
标签的defer或async属性
当然除了本文介绍的方法之外,还有很多方法,例如:
-
使用XHR对象下载Javascript代码并注入页面中
-
使用动态创建的
<script>
元素来下载并执行代码
通过以上策略,可以极大提高那些需要使用大量Javascript的Web应用的实际性能!