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

相关推荐
undefined&&懒洋洋2 分钟前
Web和UE5像素流送、通信教程
前端·ue5
大前端爱好者2 小时前
React 19 新特性详解
前端
随云6322 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
随云6322 小时前
WebGL编程指南之进入三维世界
前端·webgl
寻找09之夏3 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
非著名架构师3 小时前
js混淆的方式方法
开发语言·javascript·ecmascript
多多米10054 小时前
初学Vue(2)
前端·javascript·vue.js
敏编程4 小时前
网页前端开发之Javascript入门篇(5/9):函数
开发语言·javascript
柏箱4 小时前
PHP基本语法总结
开发语言·前端·html·php
新缸中之脑4 小时前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法