一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情。
多数浏览器都是利用单一的进程来处理界面(UI)刷新和 JS 脚本执行,所以同一时刻只能做一件事。这就意味着 JS 脚本的解析和执行都会阻塞页面的下载和渲染。
脚本的位置
理论上来说,把样式和行为相关的脚本放在一起,并先加载他们这样子有助于确保页面渲染和交互的正确性。例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./file1.js"></script>
<script src="./file2.js"></script>
<script src="./file3.js"></script>
<link rel="stylesheet" href="./index.css">
<title>Document</title>
</head>
<body>
<p>hello world</p>
</body>
</html>
有一个严重的性能问题:页面白屏时间过长。
在 <head> 中加载了三个 JS 文件,由于脚本会阻塞页面渲染,直到它们全部下载并执行完成后页面才会继续渲染。而在浏览器解析到 <body> 标签之前不会渲染页面的任何部分,所以会导致首次渲染时白屏时间过长。
因此我们插入 JS 脚本的位置尽可能放到 <body> 标签的底部。
如果把一段内嵌脚本放在<link> 标签之后会导致页面阻塞去等待样式表的下载。这是浏览器为了确保脚本执行的时候能够获得最精准的样式信息。
因此建议不要将内嵌脚本放在<link> 标签之后。
由于每个<script> 脚本都会有一个解析和执行的过程,在考虑到 HTTP 请求会带来额外的性能开销,下载单个100KB 的文件要比4个25KB的文件速度要快。
因此减少页面中的<script>标签数量有助于减少页面首次渲染的时间。
脚本阻塞
尽管下载单个较大的 JS 文件只产生一次 HTTP 请求,却会阻塞浏览器一大段时间。为了避免这种情况,又需要向页面中逐步加载多个 JS 文件,在某种程度上是不会阻塞浏览器。
最优的解决方案是在页面加载完成之后再加载 JS 文件,也就是在触发 load 事件后再下载脚本。
- 延迟脚本
-
往<script> 脚本中添加 defer 字段,可以存放在文档的任何位置。它不会阻塞浏览器的其他进程,对应的 JS 文件将在页面解析到<script>标签时开始下载,但不会立即执行,直到触发
load事件前。 -
往<script>标签中添加 async 字段, 可以存放在文档的任何位置。对应的 JS 文件将在页面解析到<script>标签时开始下载,但是执行是异步的而且执行的时候也不会根据它们的的出现顺序来执行。但是脚本都会在页面的load 事件前执行,但可能会在
DOMContentloaded之前或之后。
- 动态脚本
- 由于可以在 JS 脚本中操作 DOM 元素,而 <script> 标签也是可以通过 JS 实现动态创建。
var script = document.createElement('script');
script.src = "./file1.js"
document.getElementsByTagName("head")[0].appendChild(script);
使用动态脚本下载文件时,返回的代码通常会立刻执行,但是当代码包含供页面其他脚本调用的接口时,必须确保脚本下载完成且准备就绪。可以通过 动态<script>标签触发的事件控制。
- 可以通过
AJAX,用它来下载 JS 文件,最后创建动态的<script>标签。