前端性能优化之浏览器关键渲染路径

写在前面

关于前端性能优化的文章非常多,写浏览器关键渲染路径的也不少,但总是感觉哪里错了或者哪里疏忽了,于是自己写一篇,同时也是最近面试的一篇总结~
下面分别从浏览器渲染过程文档资源的加载与阻塞关键渲染路径及优化等三个角度来看。

浏览器渲染过程

面试常被问到 “前端怎样性能优化”,我自己在回答时总是会按照 “浏览器从输入一个 URL 到页面显示经历了什么” 的思路去回答,这样的回答既显条理,又不易因紧张而将一些简单的性能优化法忘记。扯回来,浏览器渲染可以是这个题目的最后一个环节,先上一张自己画的图:

浏览器渲染过程

类似的图也有不少,但大同小异,大概即是浏览器首先加载 HTML 文档,过程中遇到了其他资源会同时加载并解析其他资源,最后生成 Rendering tree 经过 layout 与 paint 显示到页面。需要注意的是,html 渲染为增量式渲染,浏览器无需等待 html 加载完便开始解析构建 DOM。这张图有助于后面的理解,但不再细说。

记:哪些操作会触发 reflow ?哪些会触发 repaint ?

HTML / CSS / JS 的加载与阻塞

关于加载

需要明确,.html / .css / .js 包括图片音频等其他资源,在加载上几乎完全并行(几乎?请看下文),不存在阻塞一说。诚然,我们访问 example.com/index.html 时需要解析到 <link rel="stylesheet" href="/style.css"> 才会去加载 style.css,但这并不影响浏览器同时去加载另外的资源。

  1. 加载:汉语词语,字面意思是增加装载量。现多用于计算机相关领域,表示启动程序时文件或信息的载入。英译 “load”。(来自百度百科)
  2. 在前端这里,“加载” 就是下载。
  3. 很多文章将 “加载 (load)” 与 “解析 (parse)” “渲染 (layout+paint)” 混淆,我认为是不妥的,本文会一一讲到。

资源之间的加载是并行,互不影响的,但是加载也是有限制的,现代浏览器对同一域名下的最大并发连接数一般是 6,也就是说如果同时对同一域名发起超过 6 个连接,超出的请求就会被阻塞。常看到 js css 等静态文件使用 CDN 托管,一个原因即是通过使用多域名并发加载,提高加载速度。

但加载需要时间,现代浏览器为了性能考虑,不会无限制的等待资源加载,对资源的加载会有失效时间。在 Chrome 60 中测试加载 js / css 文件时最多会等待 4 min,若 4 min 内服务器无任何响应数据返回,则会触发加载错误,会在 console 输出 net::ERR_EMPTY_RESPONSE 错误提示,同时浏览器继续解析。但若 4 min 内返回部分响应数据,则会继续等待完整的响应返回(即会突破 4 min 的失效时间限制,我测试了 13 min 仍然没有报错我放弃了,推测浏览器没有对此做进一步的时间限制,这是合理的),这里与服务器推送技术 long-polling 相似。见下图:

net::ERR_EMPTY_RESPONS 错误

关于阻塞

明确了这点,那么资源之间的阻塞是怎样的?阻塞是指资源加载完之后按照一定的顺序解析 (parse) (或 html 文档的渲染等)(注意这里的顺序不是指完全的串行),有顺序就有阻塞,下面讲到 css 阻塞 html 渲染,js 阻塞 DOM 树构建,css 阻塞 js 的解析,js 阻塞一切等:

  1. css 阻塞 html 渲染 (layout)
    试想,如果 css 不阻塞 html 渲染,那么浏览器会先将无样式的 html 渲染出来,然后突然产生样式,即 FOUT(Flash Of Unstyled Text)。因此现代浏览器中,通过内联 <style>,外联 <link> 以及
    document.write('<link ...>') 等引入的 css 均会阻塞 html 渲染,但不会影响 html 构建 DOM 树。
    需要注意的是,这与 css 在文档中引入位置无关,考虑下面的代码:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    
    <body>
        <h1>hello, zphhhhh</h1>
        <link rel="stylesheet" href="/css/style.css">
    </body>
    
    </html>
    

    上面代码若 css 文件加载需要 2s,文档会在 2s 后才显示出来,但 DOM 树早已构建完毕,就等 CSSOM 了。但也存在 css 不阻塞的情况,比如

    • 媒体查询不符合时,会同时加载但不会阻塞 html 渲染:
      <link rel="stylesheet" href="index_print.css" media="print">
      
    • 使用 DOM API 动态生成 link:
      document.createElement('link');
      
    • CSS preload,是 Resource Hints 规范,兼容性问题不再细说,可自行了解。
      <link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet'">
      
  2. js 阻塞 DOM 树构建(没有 DOM 树就没有 HTML 渲染)
    js 可以通过 document.write 修改 HTML 文档流,因此在执行 js 时,浏览器会停止构建 DOM,但由于浏览器的增量构建,浏览器可能会渲染出一部分 DOM( js 之前),考虑下面的代码:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    
    <body>
        <h1>hello,</h1>
        <script src="/main.js"></script>
        <h1>zphhhhh</h1>
    </body>
    
    </html>
    

    上面代码若 js 文件加载需要 2s,文档会先显示 hello,,2s 后再显示 zphhhhh。但也存在 js 不阻塞 DOM 构建的情况,可以:

    • 加入 async / defer 属性(只是有可能不阻塞),声明 js 中没有使用 document.write(),二者区别在于,声明 async 会在 js 加载完后立即解析执行,而声明 defer 在 js 加载完仍需等待 DOM 树构建完成(即推迟执行),看图就明白:

      async VS defer

      如下代码:

      <script async src="./main.js"></script>
      或
      <script defer src="./main.js"></script>
      

      但这也意味着 js 中真的不能使用 document.write() 了,强行使用会有提示,且没有生效,但同一文件中其他代码仍可正常执行:

      Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
      
    • 使用 DOM API 动态生成 script

      document.createElement('script');
      
  3. css 阻塞 js 解析(执行)
    由于 js 可能会读取或修改 CSSOM,因此需等待CSSOM构造完成后,js 才能执行。考虑如下代码:

    <body>
        <h1>hello,</h1>
        <link rel="stylesheet" href="/css/style.css">
        <script src="/main.js"></script>
        <h1>zphhhhh</h1>
    </body>
    

    若 css 加载 2s,则 js 会等待 css 的加载和构建 CSSOM,完毕后才会执行。当然,若上例的 js 声明了 async / defer,则以 async / defer 的约定执行,即可能会 js 不被 css 阻塞。

  4. 扩展上述第 2 点即为 js 的解析执行几乎阻塞一切
    不难理解,浏览器 js 的单线程模型也使其更加简单,js 在解析执行期间几乎会阻塞一切,当然是指阻塞一切其他资源的解析构建渲染等等,不包含加载。

浏览器关键渲染路径

理清了 html / css / js 等资源的加载与阻塞,终于可以开心的继续理解浏览器的关键渲染路径了。关键渲染路径什么鬼?这是指浏览器从最初的加载资源到第一次显示内容需要的资源与时间,考虑下面代码:

<html>
  <head>
    <link rel="stylesheet" href="style.css">
    <script src="index.js"></script>
    <script src="baidu_tongji.js"></script>
  </head>

  <body>
  </body>
</html>

此时关键资源数有 1*.html + 1 * .css + 2 * .js = 4 个,尝试改为:

<html>
  <head>
    <style>
        /* style.css */
    </style>
    <script>
        // index.js
    </script>
    <script defer src="baidu_tongji.js"></script>
  </head>

  <body>
  </body>
</html>

则关键资源数只有 1*.html 个,当然这只是一个最简单的模型,实际操作当然不能把所有 js css 写到 html 中,意会就好~

优化的角度来看,可以从下面几个方面考虑:

  1. 考虑到浏览器对同一域名最大并发连接限制,可以减少 http 请求,合并请求,比如合并 css,打包 js,小文件使用 base64 等,Webpppppack~
  2. 适量的多域名提高并发数量,但不能太多,因为 DNS 解析等也需要花费时间。
  3. 启用压缩,压缩静态资源体积。
  4. 启用缓存。
  5. 以及上面讲 “加载与阻塞” 提到的方法~

// 暂时想到这么多,经验不足如本文有错误之处,望请及时指出,免误后人,非常感谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容