浏览器渲染之link标签和script标签对页面的影响

3,083 阅读7分钟

一、引子

最近看了本书,叫《web 高效编程与优化实践》,看的过程中激发了我对 Chrome Devtools 的兴趣,恰好看到了关于性能优化和网页渲染的那部分,又想起了之前看过各大论坛大家对于浏览器渲染的理解,所以打算自己再看看这部分的知识点,解决一些疑问。

本文的试验都是基于 Chrome 浏览器,版本:75.0.3770.100(正式版本)(64 位)

二、前置知识

先上链接:

这几篇部分是在这个过程中看到的感觉比较细致且靠谱的文章,虽然文章的写作时间都不算早,但是原理基本不会有太大变化,大家如果看完我的文章觉得不过瘾的话,可以看看这些文章。

先简单概括一下浏览器渲染的步骤:

  1. 浏览器接收到 html 文档
  2. 解析 html 文档生成 dom 对象模型,是一个树形结构。
  3. 发起请求,获取 html 中的外部资源,比如图片,css文件,js文件等。
  4. 获取css文件后,生成 css 对象模型,也是一个树形结构。
  5. 有了 dom 对象模型树和 css 对象模型树,浏览器会生成渲染树(render tree)。
  6. 有了渲染树,浏览器会执行一个叫做布局的操作(layout),用于计算页面上各个元素的几何位置。
  7. 最终,浏览器会把“布局”好的页面元素绘制(paint)成我们最终看到的网页。

以上步骤说的比较简略,而且没有带上 reflow 和 repaint,主要是帮大家梳理一下这个过程,便于后面的阅读。

三、正文

终于说到文章正题,想必标题大家也看到了,是想研究一下 link 标签和 script 标签对页面渲染的影响,既然是影响,肯定是不理想的情况。至于对页面渲染的影响,这里主要讨论的是对页面首次绘制时的影响。

下面让我们先整理一下不理想的情况,然后一个一个来试验下:

  • link 标签正常位置加载时间过长
  • link 标签非正常位置加载时间过长
  • script 标签正常位置加载时间过长
  • script 标签非正常位置加载时间过长

这就是本文试验的一些情况,正常位置当然是我们平时总说的 link 标签要放在首部 head 中,script 标签放在 body 底部,加载时间过长通过服务器端延时 5000 毫秒来处理。

本次为了快速开发,用到了一些快速搭建的工具,先推荐给大家:

  • Parcel:一个小型前端打包工具,很方便就能起个服务,之前用过一次,感觉很好用。
  • express:因为要模拟网络不好的情况,虽然谷歌浏览器里也可以设置网络速度,但是我如自己控制来的直观,所以还是自己简答搞个 server 靠谱些,express 就不多介绍了。
  • nodemon:用了这个来监测 server 端的文件修改,其实写好后也没啥改动,就是修改一下不同文件返回时的延迟时间。

行了,就用到这些东西,废话不多说,我们开始吧。

先来看看我们用到的文件:

html:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        img {
            width: 500px;
        }
        .block {
            width: 300px;
            height: 200px;
            border: 1px solid #000;
        }
    </style>
    <link href="http://127.0.0.1:3000/public/css/common.css" rel="stylesheet">
</head>

<body>
    <div class="block">
        <h2 class="title">我是一段没有感情的测试文案</h2>
    </div>
    <div class="pic">
        <img src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4215863670,261223381&fm=26&gp=0.jpg">
    </div>
    <script src="http://127.0.0.1:3000/public/js/test.js"></script>
</body>
</html>

common.css:

.block {
	width: 500px;
	background-color: red;
	border: none;
}

test.js:

document.querySelector('.title').innerHTML = '我被修改了';

app.js:

const express = require('express')
const app = express()
const fs = require('fs')
const path = require('path');

app.use('*',function(req, res){
    if (req.params[0] === '/public/css/common.css') {
        // setTimeout(() => {
            res.setHeader('content-type', 'text/css');
            res.send(fs.readFileSync(path.join(__dirname, req.params[0])));
        // }, 5000)
    } else if(req.params[0] === '/public/js/test.js') {
        // setTimeout(() => {
            res.send(fs.readFileSync(path.join(__dirname, req.params[0])));
        // }, 5000)
    } else {
        res.send('success');
    }
})

app.listen(3000, () => console.log('working'))

这个页面打开以后是这个样子的:

情况一:js在页面顶部延迟加载

让我们先把 js 标签放到 head 的底部,也就是 link 标签下面(这里为了不把每次改完标签位置的html都帖出来,我每次说的位置都是以初始位置为基准),并把服务器端延时的代码加上来看看效果:

可以看到页面因为 js 脚本的延迟加载,导致页面白屏长达数秒,这肯定不是我们平时开发中想要的,还有因为脚本位置过前导致操作的 dom 元素也无法找到。

再让我们看看控制台中这个页面具体经历了什么:

通过 performance 我们可以看到页面的在 5s 后才开始首次绘制,timeline 上也很容易看出前面页面一直处于白屏阶段。

情况二:js在页面底部延迟加载

现在让我们把 js 放到初始的位置,同样加上延迟,再看下效果:

这次我们可以看到虽然 js 文件延迟加载了,但是 js 文件之前的元素很快就渲染出来了,再看下控制台:

这次就很快了,页面没有因为 js 的加载阻塞前面元素的渲染,js 文件加载后因为修改了 dom 元素又绘制了一次。所以说把 js 文件放在页面底部这个做法还是比较合理的,但是也需要分情况讨论,比如有些页面较长,图片较多,可能首屏只展现了部分 dom,这时如果把所有的 js 都放在页面底部,反而可能会影响 js 脚本对 dom 操作的实时性。

情况三:css在页面顶部延迟加载

看完了 script 标签,现在让我们看下 link 标签的表现,首先把 script 标签的延迟去掉,让 link 标签待在它原来的位置,在服务端加上延迟试一下:

不好意思,这里没有动图。。。

这次的情况动图可以参考动图一,因为第一次正经写文章,所以 gif 图是先录屏,然后截视频,再上传视频做 gif。有点麻烦,就懒得再搞一个了,小伙伴有好的软件或者方法欢迎评论区推荐。 至于网页的 performance 和第一种情况也是基本无差,咱们直接进入到下一种情况吧。

情况四:css在页面底部延迟加载

这次我们把 link 标签放到 body 的底部,其实前三种的情况和我想象中的基本没差,我试这个就是想看看最后一种情况,来直接看图吧:

诶,貌似和我预想的不太一样,我们先来看下控制台确认下:

我们可以看到浏览器这次并没有像 script 脚本延迟时一样,等到 css 加载完才开始绘制,而是很早就把 dom 画出来了,等 css 加载完后,又再次绘制了一遍。后来我多试了几次,如果把css文件放到dom中间位置,发现css文件的延迟同样会阻塞后面元素的渲染,这个是我之前没有想到的。

四、结语

其实,如果网速足够快,在资源的加载这一块,我们根本无需考虑优化,因为脚本本身不会大的离谱,所以在网速超快的情况下,当然是想放哪里放哪里。可能在5年、10年以后的开发者回过头来我这篇文章,没准会对我现在考虑的这些问题不屑一顾,hhh。