高性能JavaScript之加载与执行——JS真的会阻塞你的页面渲染!

script标签会阻塞页面的渲染!

浏览器并未规定我们要将script标签放置在何处,你可以把它放在body的前面,中间,后面,甚至是放在head里。当浏览器解析到一个script标签时,它会先加载这个脚本,然后解析与执行它,而这个过程会中断后续HTML内容的解析与渲染。

考虑下面的示例,浏览器会像一个流水线一样,先解析"Hello World"这个标题并在页面上渲染出来,然后加载和执行"longtask.js"脚本,一切完成后再继续解析和渲染"你好"这个标题。如果longtask.js的加载与执行一共要花费一秒,那么页面效果会是这样:先看到"Hello world",过了一秒之后,再出现"你好"~

html 复制代码
<body>
  <h1>Hello World</h1>
  <script src="longtask.js"></script>
  <h1>你好</h1>
</body>

当你尝试这个示例后,就能在心中确定:script脚本真的会影响我页面的渲染呀!我可不想自己的页面像播放视频时卡帧了一样断断续续的。

非阻塞加载

将script标签置于body底部

要想不阻塞html页面内容的渲染,一个最简单的办法就是把script标签放在body的底部:等其他html内容处理完了再解析script标签。

html 复制代码
<body>
  <h1>Hello World</h1>
  <h1>你好</h1>
  <script src="longtask.js"></script>
</body>

如果要实现script放在其他任何地方都不阻塞页面渲染,则可以使用defer属性。

defer

html 复制代码
<head>
  <script defer src="longtask.js"></script>
</head>
<body>
  <h1>Hello World</h1>
  <h1>你好</h1>
</body>

带有defer的脚本会立即下载,但延迟执行:直到整个HTML文档解析和渲染完成后再执行。有一个事件是DOMContentLoaded,它会在HTML完全解析,且所有defer脚本下载与执行后触发。

html 复制代码
<head>
  <script defer src="longtask.js"></script>
</head>
<body>
  <h1>Hello World</h1>
  <h1>你好</h1>
  <script>
    document.addEventListener("DOMContentLoaded", (event) => {
      console.log("DOMContentLoaded");
    });
  </script>
</body>

create-react-app脚手架生成的应用在打包后也会生成这样一个带defer的脚本来加载JS(对于html文档,defer="defer"defer两种写法没有区别。如果文件是.xhtml的文档,则要求使用前者)。

由于defer脚本是在所有HTML内容解析完之后执行,所以即使它放在head中,我们仍然可以在JS代码中获取body元素的内容

js 复制代码
console.log(document.getElementById("root")); // <div id="root"></div>

目前defer属性几乎是所有浏览器都支持的,可以放心使用。在任何情况下,我们都应该使用defer来避免script标签阻塞渲染,除非你真的有必要在所有HTML内容渲染完成前打断它。

async

async(异步)脚本同样不会阻塞HTML的解析,它与defer的不同是:它在下载完成后会立刻执行,而不关心所有HTML页面内容是否已经加载与解析完成。考虑下面一个例子:

html 复制代码
<head>
  <link rel="stylesheet" href="style.css" />
  <script async src="main.js"></script>
</head>
<body>
  <div id="app"></div>
</body>

style.css文件的内容如下:

css 复制代码
div {
  width: 200px;
  height: 200px;
}

在main.js中获取id为"app"的元素并打印:

js 复制代码
console.log(document.getElementById("app").clientHeight)

最终main.js里打印的结果是什么呢?可能是200,也可能是0!这取决于style.css文件是否在main.js加载执行前完成了加载与解析。另外,多个async文件也是不保证执行顺序的,并非代码中位置在前面的async脚本就一定先执行。

所以,async适用于与页面内容无关,同时也与其他JS文件无关的独立脚本。它就像一个独行侠🚀,不要期望它会听你的指令在某个具体时机执行。

使用行内代码还是外部文件

JavaScript代码也是可以内联的,像下面这样:

html 复制代码
<body>
  <div id="app"></div>
  <script>
    console.log("Hello World");
  </script>
</body>

考虑下如果我们把所有的JS代码都内联到HTML文件中会怎样呢?那这个HTML体积会很大,网络加载时间会更久,页面白屏时间也就会更长。另外HTML文件一般是不设置缓存的,所以即使这些JS代码没有变更,也需要每次都重新加载。

所以,实际项目中倾向于把这些JS代码保存到独立文件中,并设置一定的缓存策略。同时,如果有多个小的JS文件,也会把它们合并为一个,以减少请求数量。

相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax