高性能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文件,也会把它们合并为一个,以减少请求数量。

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄2 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端