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

相关推荐
lichenyang4531 分钟前
React ajax中的跨域以及代理服务器
前端·react.js·ajax
呆呆的小草3 分钟前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
WHOAMI_老猫7 分钟前
xss注入遇到转义,html编码绕过了解一哈
javascript·web安全·渗透测试·xss·漏洞原理
一 乐1 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
testleaf2 小时前
前端面经整理【1】
前端·面试
好了来看下一题2 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron
啃火龙果的兔子2 小时前
前端八股文-react篇
前端·react.js·前端框架
小前端大牛马2 小时前
react中hook和高阶组件的选型
前端·javascript·vue.js
刺客-Andy2 小时前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
秋田君3 小时前
深入理解JavaScript设计模式之策略模式
javascript·设计模式·策略模式