一零四、前端性能优化详解

  • 1 前端性能优化
    • 介绍
    • 页面性能优化
      • 浏览器
        • 浏览器的主要作用
        • 浏览器的组成结构
        • 浏览器是多进程的
        • 浏览器的渲染机制
        • 重排reflow与重绘repaint
      • 页面加载缓慢的原因
        • 浏览器部分
        • 代码部分
      • 优化原则
        • 尽量减少HTTP请求
        • 使用内容传送网络CDN
        • 避免空src或者是href值
        • gzip的组件
        • CSS放在顶部,JS放在底部
        • 减少DNS查找
        • 压缩资源
        • 避免3xx/4xx
        • AJAX优化
        • Cookie优化
        • 利用缓存
        • 缩短服务器响应时间
      • 优化工具
        • Google Page Speed
        • JsPerf & Benchmark.js
    • 代码优化——写出优质的代码
    • JsPerf
      • JsPerf使用简介
        • 线上使用方案
        • 线下使用方案:
      • 代码示例for vs foreach
      • 前端项目优化实践
    • PageSpeed
      • PageSpeed简介
        • 使用[Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/)
        • 使用Chrome插件/Mozila插件
      • 前端项目优化实践
    • 补充学习
      • 编码、解码形成DOM树的过程
      • 优化工作清单
      • 优化关键渲染路径
      • 其他JS在线测试网站
      • 参考资料

1 前端性能优化

介绍

根据当今网络上的所有主要浏览器公司的说法,如果每个网站都想要排名靠前,表现良好并确保客户满意,那么每个网站都需要快。毕竟,如果您的网页加载速度很慢,那么您会立即增加客户开始在其他地方搜索信息,而不是坚持下去的风险。

这也是为什么很多公司去找专门的技术人员来做SEO(Search Engine Optimization),即搜索引擎优化。 因为,页面的加载会影响到爬虫的爬取,页面加载速度是一项非常重要的指标。况且,页面优化直接影响到了用户体验。所以学习前端性能优化势在必行。

经常有面试官会问:从用户输入浏览器输入url到页面最后呈现 有哪些过程?有什么办法能优化页面性能?

答案大致如下:

  1. 用户输入URL地址
  2. 浏览器解析URL解析出主机名
  3. 浏览器将主机名转换成服务器ip地址(浏览器先查找本地DNS缓存列表 没有的话 再向浏览器默认的DNS服务器发送查询请求 同时缓存)
  4. 浏览器将端口号从URL中解析出来
  5. 浏览器建立一条与目标Web服务器的TCP连接(三次握手)
  6. 浏览器向服务器发送一条HTTP请求报文
  7. 服务器向浏览器返回一条HTTP响应报文
  8. 关闭连接 浏览器解析文档
  9. 如果文档中有资源 重复6 7 8 动作 直至资源全部加载完毕

再深一点层次:会问到渲染机制,重排、重绘

重排:

  1. 定义:DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow。

  2. 触发Reflow:

    • 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint
    • 当你移动DOM的位置,或是搞个动画的时候
    • 当你修改CSS样式的时候
    • 当你Resize窗口的时候(移动端没有这个问题),或者是滚动的时候
    • 当你修改网页的

    最常问:如何减少Reflow?或者避免Reflow?

重绘:

  1. 定义:当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。

    页面要呈现的内容,通通都绘制到页面上。

  2. 触发Repaint

    • DOM改动
    • CSS改动

    最常问:如何避免最小程序的Repaint?

既然页面的性能优化这么重要,**你知道有哪些原因是影响页面性能的?**比如…

  • HTTP请求
  • 复杂的页面逻辑(JS设计)
  • 重度的DOM操作
  • 服务端响应
  • 大量的数据

有哪些办法可以加速我们的页面性能?比如…

  • 资源压缩与合并(代码打包)
  • 异步加载
  • CDN
  • DNS预解析
  • 缓存

主要内容:

  • 页面加载缓慢原因分析
  • 性能优化原则
  • 优化工具介绍
  • 浏览器的工作机制,了解一些基本常识的原理:
    1. 为什么要将js放到页脚部分
    2. 引入样式的几种方式的权重
    3. css属性书写顺序建议
    4. 何种类型的DOM操作是耗费性能的

收获?

  • 前端页面优化,为成为SEO路添砖加瓦
  • 写出优质代码,页面性能蹭蹭蹭的往上走
  • 面试如果提到页面优化,可以娓娓道来,牛皮吹的飞起…
  • 几种常见的页面优化工具的使用PageSpeed,JSPerf

准备:

  • 注册github账号

页面性能优化

前端性能优化可以分为两大部分:浏览器部分、代码部分。

浏览器部分又可以分为:

  • 网络层面
  • 浏览器渲染层面
  • 服务端层面

代码部分又可以分为:

  • 构建层面
  • 编码层面
  • 机制(SSR,英文Server Side Render:服务器端渲染)
  • 规范

首先来看看浏览器部分:前端的页面主要在浏览器上运行着,那么我们追根溯源,从浏览器的原理开始,研究如何进行页面性能优化。

浏览器

目前使用的主流浏览器有五个:Internet Explorer、Firefox、Safari、Chrome 浏览器和 Opera。本文中以开放源代码浏览器为例,即 Firefox、Chrome 浏览器和 Safari(部分开源)。根据 StatCounter 浏览器统计数据,目前(2019年 6 月)Firefox、Safari 和 Chrome 浏览器的总市场占有率 62.7%。由此可见,如今开放源代码浏览器在浏览器市场中占据了非常坚实的部分。

浏览器的主要作用

浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示您选择的网络资源。这些网络资源包括以下内容:

  • HTML
  • CSS
  • JavaScript的
  • 媒体(图片,视频等)

也可以分为HTML文档(HTML/CSS/JS)、PDF、图片、视频和其他类型。

浏览器解释并显示 HTML 文件的方式是在 HTML 和 CSS 规范中指定的。这些规范由网络标准化组织 W3C(万维网联盟)进行维护。
多年以来,各浏览器都没有完全遵从这些规范,同时还在开发自己独有的扩展程序,这给网络开发人员带来了严重的兼容性问题。如今,大多数的浏览器都是或多或少地遵从规范。

浏览器的用户界面有很多彼此相同的元素,其中包括:

  • 用来输入 URI 的地址栏
  • 前进和后退按钮
  • 书签设置选项
  • 用于刷新和停止加载当前文档的刷新和停止按钮
  • 用于返回主页的主页按钮

奇怪的是,浏览器的用户界面并没有任何正式的规范,这是多年来的最佳实践自然发展以及彼此之间相互模仿的结果。HTML5 也没有定义浏览器必须具有的用户界面元素,但列出了一些通用的元素,例如地址栏、状态栏和工具栏等。

有一些比较有趣的事情:

Safari是乔布斯在2003年的Macworld大会发布的,9年过去了,08年才发展起来的Chrome用了不到Safari一半的时间远远把Safari甩在了后面,这让Safari情何以堪。

两大浏览器都是基于苹果的布局引擎Webkit,按道理Safari是占优势的,因为两者都分别是自己操作系统的默认浏览器,许多人就顺便使用了,懒得去下载别的浏览器。

许多人可能会想,这还不简单,Chrome运行于Windows操作系统,而Safari大部分时候都用于苹果自己的系统,Windows .VS. OS X,哪个使用人群广?很显然是前者,所以Chrome当然比Safari发真快,可是,实际上Safari要比Chrome更早登陆Windows。

在2007年六月,Safari for Windows Beta版就已经发布,正式版于2008年三月发布,而Chrome直到2008年的九月才发布,Chrome只用了一年的时间就超过了Safari。

大量Benchmark测试表明Chrome不管在网页打开还是JavaScript表现方面,速度都最快。

但是,当Safari发布Windows版的时候,测试结果也是一样的,不管在Mac还是PC上测试,那时候都是最快的,如果仅仅是因为速度,那么Safari在2007年六月的表现就应该和chrome在2008年九月的时候一样。

难道Chrome胜在插件?也许,不过Safari在2010年年中的时候也有了许多插件,当然,Chrome的插件质量更好更丰富,如果这也足够让Safari落后于Chrome,那么开发商们可能早就将游戏植入Chrome,而且,Firefox也先于Chrome和Safari植入插件,现在不也败给了Chrome吗?

主要原因:

  1. Safari用起来不舒服,google推广给力
  2. 独特的用户体验(UI、交互、云、Google全家桶)

那么Safari就一文不值了?

但是,Mac上的用户使用safari更加的省电、省资源,稳定性会更好。**插件不需要爬梯子!!!**更低的学习成本。

IE(Internet Explorer)的黑历史:

微软为了对抗当时的主流浏览器 NetScape,准备开发自己的浏览器并在 Windows 中默认捆绑销售。但是,留给微软的时间并不充裕,他们没时间从零开始,于是和 Spyglass 合作开发,于是,计算机历史上最著名也最臭名昭著的浏览器 IE 诞生了。因为强大的功能和捆绑策略,IE 迅速击败了 NetScape,成为浏览器市场的绝对霸主。

在 2002 年,IE 已经拥有了95%的市场份额,几乎打败了所有的竞争对手。大为僵,又不思进取,于是颠覆者前仆后继。Firefox 开始迅速崛起,Chrome 后来居上,反观 IE,出现了各种安全漏洞和兼容性问题。2006年,IE 被评为“史上第八糟科技产品”。在相当长的时间里,IE 浏览器是前端程序员员的噩梦

带给我们的思考:

  • 软件迭代更新的重要性(居安思危)
  • 用户体验、用户感知的重要性(人性化产品)
  • 互联网时代,什么都是可以替代的
  • 分享、免费、共筑共赢才是互联网的精神

浏览器的组成结构

一零四、前端性能优化详解_第1张图片

  1. 用户界面(User Interface) - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。

  2. 浏览器引擎(Browser engine) - 在用户界面和渲染引擎之间传送指令。

  3. 渲染引擎(Rendering engine) - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。

    也可以叫呈现引擎(Rendering Engine)或者布局引擎(Layout Engine)

    默认情况下,渲染引擎可显示 HTML 和 XML 文档与图片。通过插件(或浏览器扩展程序),还可以显示其他类型的内容;例如,使用 PDF 查看器插件就能显示 PDF 文档。但是在本章中,我们将集中介绍其主要用途:显示使用 CSS 格式化的 HTML 内容和图片。

    浏览器(Firefox、Chrome 浏览器和 Safari)是基于两种渲染引擎构建的。Firefox 使用的是 Gecko,这是 Mozilla 公司“自制”的渲染引擎。而 Safari 和 Chrome 浏览器使用的都是 WebKit。

    浏览器 渲染引擎(开发语言) 脚本引擎(开发语言)
    Chrome Blink (c++) V8 (c++)
    Opera Blink (c++) V8 (c++)
    Safari Webkit (c++) JavaScript Core (nitro)
    FireFox Gecko (c++) SpiderMonkey (c/c++)
    Edge EdgeHTML (c++) Chakra JavaScript Engine (c++)
    IE Trident (c++) Chakra JScript Engine (c++)

    Firefox在2017年启用了新的web引擎Quantum,Quantum 以 Gecko 引擎为基础,同时利用了 Rust 的良好并发性和 Servo 的高性能组件,为 Firefox 带来了更多的并行化和 GPU 运算,让 Firefox 更快更可靠。

    2015 年 3 月,微软将放弃自家Edge:转而开发Chromium内核浏览器。

    它们的开发时间轴:
    一零四、前端性能优化详解_第2张图片

    WebKit 是一种开放源代码渲染引擎,起初用于 Linux 平台,随后由 Apple 公司进行修改,从而支持苹果机和 Windows。有关详情,请参阅 webkit.org

  4. 网络(Networking) - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。

  5. 用户界面后端(UI Backend) - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。

  6. JavaScript 解释器(JavaScript Interpreter)。用于解析和执行 JavaScript 代码,如 V8 引擎。

    JS引擎线程负责解析Javascript脚本,运行代码。

    JS引擎一直等待任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中只有一个JS线程在运行

  7. 数据存储(Data Persistence)。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

浏览器是多进程的

进程线程简单的理解:进程里面可以有多个线程,进程就是QQ,线程就是会话。

  • 浏览器是多进程的
  • 浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
  • 简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。

浏览器里面的进程:

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有

    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等
  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建

  3. GPU进程:最多一个,用于3D绘制等

  4. 渲染进程(浏览器内核)(Renderer进程,内部是多线程的)

    • 默认每个Tab页面一个进程,互不影响。
    • 主要作用为页面渲染,脚本执行,事件处理等

渲染进程是多线程的:

  1. GUI渲染线程

    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
    • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
  2. js引擎线程

    • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
    • JS引擎线程负责解析Javascript脚本,运行代码。
    • JS引擎一直等待任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中只有一个JS线程在运行
    • 同样注意,GUI渲染线程与JS引擎线程是互斥的。所以如果JS执行的时间过长,要放在body下面,否则就会导致页面渲染加载阻塞。
  3. 事件触发线程

    • 管理着事件队列
    • 监听事件,符合条件时把回调函数放入事件队列中
  4. 定时触发器线程

    • setInterval与setTimeout在此线程中计时完毕后,把回调函数放入事件队列中
    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确),因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms
  5. 异步http请求线程

    • 检测到XHR对象状态变化时,将回调函数放入事件队列中
    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

    了解 一下,执行线:
    一零四、前端性能优化详解_第3张图片

渲染线程与JS引擎线程互斥

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起, GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

举一个简单的例子:


<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Documenttitle>
    <script>
      // 页面上的hello world不会变成红色
      document.getElementById("app").style.color = "red";
    script>
  head>
  <body>
    <div id="app">
      hello world!
    div>
  body>
html>

JS阻塞页面加载

从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。

譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。 然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。

所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

总结一下:

  • css加载不会阻塞DOM树解析(异步加载时DOM照常构建),但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)
  • Javascript 阻塞 DOM 解析

浏览器的渲染机制

对于渲染,我们首先需要了解一个概念:设备刷新率。

设备刷新率是设备屏幕渲染的频率,通俗一点就是,把屏幕当作墙,设备刷新率就是多久重新粉刷一次墙面。基本我们平常接触的设备,如手机、电脑,它们的默认刷新频率都是60FPS,也就是屏幕在1s内渲染60次,约16.7ms渲染一次屏幕。

这就意味着,我们的浏览器最佳的渲染性能就是所有的操作在一帧16.7ms内完成,能否做到一帧内完成直接决定着渲染性,影响用户交互。

渲染引擎一开始会从网络层获取请求文档的内容,内容的大小一般限制在 8000 个块以内。

然后进行如下所示的基本流程
在这里插入图片描述

渲染引擎将开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的 DOM 节点。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。

呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。

呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制 - 渲染引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。

需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,渲染引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。

那么我们知道了,CSSOM 树和 DOM 树合并成渲染树,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上。优化上述每一个步骤对实现最佳渲染性能至关重要。

具体的流程:

  • DOM 树与 CSSOM 树合并后形成渲染树。
  • 渲染树只包含渲染网页所需的节点。
  • 布局计算每个对象的精确位置和大小。
  • 最后一步是绘制,使用最终渲染树将像素渲染到屏幕上。
    一零四、前端性能优化详解_第4张图片

请注意 visibility: hiddendisplay: none 是不一样的。前者隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框),而后者 (display: none) 将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。

我们以webkit的渲染作为示例:
一零四、前端性能优化详解_第5张图片

我们可以使用调试工具来看一个真实的例子:
一零四、前端性能优化详解_第6张图片

script标签的处理

JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器在解析HTML时,一旦碰到script,就会立即停止HTML的解析(而CSS不会),执行JS,再返还控制权。

事实上,JS执行前不仅仅是停止了HTML的解析,它还必须等待CSS的解析完成。当浏览器碰到script元素时,发现该元素前面的CSS还未解析完,就会等待CSS解析完成,再去执行JS。

JS阻塞了HTML的解析,也阻塞了其后的CSS解析,整个解析进程必须等待JS的执行完成才能够继续,这就是所谓的JS阻塞页面。一个script标签,推迟了DOM的生成、CSSOM的生成以及之后的所有渲染过程,从性能角度上讲,将script放在页面底部,也就合情合理了。

简单来说:渲染线程与JS引擎线程是互斥的,当JS引擎执行时渲染线程会被挂起(相当于被冻结了),渲染更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

总结一下:

浏览器的渲染流程分为:

  • DOM树构建
  • CSSOM树构建
  • RenderObject树构建
  • 布局
  • 绘制

重排reflow与重绘repaint

重排reflow

reflow指的是重新计算页面布局。

某个节点reflow时会重新计算节点的尺寸和位置,而且还有可能触发其子节点、祖先节点和页面上的其他节点reflow。在这之后再触发一次repaint。

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流,每个页面至少需要一次回流,就是在页面第一次加载的时候。

导致reflow的操作

  • 调整窗口大小
  • 改变字体
  • 增加或者移除样式表
  • 内容变化,比如用户在input框中输入文字
  • 激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)
  • 操作 class 属性
  • 脚本操作 DOM
  • 计算 offsetWidth 和 offsetHeight 属性
  • 设置 style 属性的值

触发页面重布局的一些css属性

  • 盒子模型相关属性会触发重布局
    • width
    • height
    • padding
    • margin
    • display
    • border-width
    • border
    • min-height
  • 定位属性及浮动也会触发重布局
    • top
    • bottom
    • left
    • right
    • position
    • float
    • clear
  • 改变节点内部文字结构也会触发重布局
    • text-align
    • overflow-y
    • font-weight
    • overflow
    • font-family
    • line-height
    • vertical-align
    • white-space
    • font-size

重绘repaint

repiant或者redraw遍历所有的节点检测各节点的可见性、颜色、轮廓等可见的样式属性,然后根据检测的结果更新页面的响应部分。

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘

只触发重绘不触发重排的一些CSS属性:

  • color
  • border-styleborder-radius
  • visibility
  • text-decoration
  • backgroundbackground-imagebackground-positionbackground-repeatbackground-size
  • outlineoutline-coloroutline-styleoutline-width
  • box-shadow

我们来看一个例子:

var bstyle = document.body.style; // cache

bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; //  再一次的 reflow 和 repaint

bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。

但是有些时候,我们的脚本会阻止浏览器这么干,比如:如果我们请求下面的一些DOM值:

减少重绘与重排:

重绘和回流在实际开发中是很难避免的,我们能做的就是尽量减少这种行为的发生。

  • js尽量少访问dom节点和css 属性,尽量不要过多的频繁的去增加,修改,删除元素,因为这可能会频繁的导致页面reflow,可以先把该dom节点抽离到内存中进行复杂的操作然后再display到页面上。(虚拟DOM)

  • 减少不必要的 DOM 层级(DOM depth)。改变 DOM 树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行 reflow 上面。

  • 不要通过父级来改变子元素样式,最好直接改变子元素样式,改变子元素样式尽可能不要影响父元素和兄弟元素的大小和尺寸

  • 尽量通过class来设计元素样式,切忌用style 多次操作单个属性

    // bad
    var left = 10,
    top = 10;
    el.style.left = left + "px";
    el.style.top  = top  + "px";
    
    // Good
    el.className += " theclassname";
    
    // Good
    el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
    
  • 尽可能的为产生动画的 HTML 元素使用 fixedabsoluteposition ,那么修改他们的 CSS 是不会 Reflow 的。

  • img标签要设置高宽,以减少重绘重排

  • 把DOM离线后修改,如将一个dom脱离文档流,比如display:none ,再修改属性,这里只发生一次回流。

  • 尽量用 transform 来做形变和位移,不会造成回流

  • 权衡速度的平滑。比如实现一个动画,以1个像素为单位移动这样最平滑,但reflow就会过于频繁,CPU很快就会被完全占用。如果以3个像素为单位移动就会好很多。

  • 不要用tables布局的另一个原因就是tables中某个元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用table的场合,可以设置table-layout为auto或fixed,

  • 避免不必要的复杂的 CSS 选择器,尤其是后代选择器(descendant selectors),因为为了匹配选择器将耗费更多的 CPU。

PS: display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。

总结一下:

重排:元素的尺寸变了、位置变了

重绘:元素的颜色、背景、边框、轮廓变了,但是,元素的几何尺寸没有变。

Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。

页面加载缓慢的原因

一零四、前端性能优化详解_第7张图片

一零四、前端性能优化详解_第8张图片

一零四、前端性能优化详解_第9张图片

浏览器部分

  • 网络层面

    1. 过多的HTTP请求

      打开一个网页的时候,后台程序的响应并不所需太多时间,等待的时间主要花费在下载网页元素上了,即HTML、CSS、JavaScript、Flash、图片等。据统计,每增加一个元素,网页载入的时间就会增加25-40毫秒(具体取决于用户的带宽情况)。

    2. 资源访问带宽小

      两方面,一方面是客户端的带宽,一方面是服务器端的带宽。

    3. 网页元素(图片、视频、样式)太大

  • 浏览器渲染层面

    1. 渲染阻塞:

      浏览器想要渲染一个页面就必须先构建出DOM树与CSSOM树,如果HTMLCSS文件结构非常庞大与复杂,这显然会给页面加载速度带来严重影响。

      所谓渲染阻塞资源,即是对该资源发送请求后还需要先构建对应的DOM树或CSSOM树,这种行为显然会延迟渲染操作的开始时间。

      JS阻塞与CSS阻塞:

      HTML、CSS、JavaScript都是会对渲染产生阻塞的资源,HTML是必需的(没有DOM还谈何渲染),但还可以从CSS与JavaScript着手优化,尽可能地减少阻塞的产生。

    2. 重复渲染

    3. DNS解析

  • 服务端层面

    1. 硬件配置低:这个是双向的
    2. 服务器软件,比如防火墙、内网策略等
    3. 未对Nginx这类web服务器进行配置优化
    4. CPU占满、数据库未优化
    5. 代码问题,代码效率,代码性能
    6. 包含了过多的分析类工具

代码部分

  • 构建层面

    未对代码进行打包、压缩、兼容性优化。

    未合并重复的请求、代码。

  • 编码层面

    没有良好的编码习惯,错误的编排JS与CSS

    for循环、迭代、同步、重定向、阻塞请求

    未删除重复、无用的代码

    未对逻辑业务复杂的代码进行重构,了解设计模式,对业务进行疏理

  • 机制(SSR,英文Server Side Render:服务器端渲染)

    未加入Async异步机制

    未思考页面加载、用户体验

  • 规范

    CSS规范

    HTML规范/HTML5规范

    Airbnb代码规范等。

优化原则

尽量减少HTTP请求

在浏览器(客户端)和服务器发生通信时,就已经消耗了大量的时间,尤其是在网络情况比较糟糕的时候,这个问题尤其的突出。

一个正常HTTP请求的流程简述:如在浏览器中输入"www.xxxxxx.com"并按下回车,浏览器再与这个URL指向的服务器建立连接,然后浏览器才能向服务器发送请求信息,服务器在接受到请求的信息后再返回相应的信息,浏览器接收到来自服务器的应答信息后,对这些数据解释执行。

而当我们请求的网页文件中有很多图片、CSS、JS甚至音乐等信息时,将会频繁的与服务器建立连接,与释放连接,这必定会造成资源的浪费,且每个HTTP请求都会对服务器和浏览器产生性能负担。

网速相同的条件下,下载一个100KB的图片比下载两个50KB的图片要耗费的网络资源更多。所以,请减少HTTP请求。

具体的方法:

  1. 组合文件,优化图片,使用sprites设计风格: 将背景图片合并成一个文件,通过background-imagebackground-position 控制显示;

    确保您的图像不大于它们所需的图像,它们采用正确的文件格式(PNG通常更适用于少于16种颜色的图形,而JPEG通常更适合照片)并且它们是针对Web压缩的。

    使用CSS sprites在网站上经常使用的图像创建模板,如按钮和图标。CSS sprites将您的图像组合成一个大图像,一次加载所有(这意味着更少的HTTP请求),然后只显示您想要显示的部分。这意味着您通过不让用户等待加载多个图像来节省加载时间。

    有一些在线工具:

    • Sprite Cow
    • Spritebox
  2. 肉联图片,使用data:URL方案将图像数据嵌入实际页面中。这可以增加HTML文档的大小。将内嵌图像组合到(缓存的)样式表中是一种减少HTTP请求并避免增加页面大小的方法。

  3. 简化页面的设计

使用内容传送网络CDN

内容分发网络(CDN),也称为内容传送网络,是用于分发传送内容的负载的服务器网络。从本质上讲,您网站的副本存储在多个地理位置不同的数据中心,以便用户可以更快,更可靠地访问您的网站。

对于初创公司和私人网站来说,CDN服务的成本可能过高,但随着您的目标受众变得越来越大并变得更加全球化,CDN对于实现快速响应时间是必要的。

请记住,最终用户响应时间的80-90%用于下载页面中的所有组件:图像,样式表,脚本,Flash等。这是Performance Golden Rule。而不是从重新设计应用程序架构的艰巨任务开始,最好首先分散您的静态内容。这不仅可以大大缩短响应时间,而且由于内容交付网络,它更容易实现。

内容传送网络(CDN)是分布在多个位置的Web服务器的集合,以更有效地向用户传送内容。选择用于向特定用户传送内容的服务器通常基于网络接近度的度量。例如,选择具有最少网络跳数的服务器或具有最快响应时间的服务器。

CDN服务商有很多,这里就不再多说:专门做CDN服务器的蓝讯、网宿、帝联、快网,还有阿里云、腾讯云、华为云等。国外如Akamai Technologies,EdgeCast或level3

除了去购买一些CDN服务商的服务以外,对于大多数开发者,可以使用公共CDN网络上的资源,如以下的方式去使用CDN加速:


<script
  type="text/javascript"
  src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"
>script>


<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js">script>


<script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js">script>


<script
  crossorigin="anonymous"
  integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
  src="https://lib.baomitu.com/jquery/3.4.1/jquery.min.js"
>script>

避免空src或者是href值

空的src和href都会导致多余的HTTP请求,虽然不影响加载时间,但是会对服务器产生不必要的流量和压力。浏览器仍然会向服务器发起一个 HTTP 请求:

  • IE 向页面所在的目录发送请求
  • Safari、Chrome、Firefox 向页面本身发送请求
  • Opera 不执行任何操作

空的src的image严重的以至于影响整个网站的用户体验,空 src 产生请求的后果不容小憩:

  • 给服务器造成意外的流量负担,尤其时日 PV 较大时;
  • 浪费服务器计算资源;
  • 可能产生报错。

有两种形式:

  1. HTML形式

    <img src="">
    <a href="">a>
    
  2. JavaScript形式

    var img = new Image();
    img.src = "";
    

解决办法:

  1. 删除空的srchref标签
  2. a标签的href属性,连接到实际的页面:
<a href="#">a>
<a href="#nogo">a>
<a href="##">a>
<a href="###">a>
<a href="void(0);">a>
<a href="void(0)">a>
<a href=";">a>
<a href="">a>
  1. 禁止跳转,添加cursor:pointer样式
<style>
    a{cursor: pointer}
style>
<a>点击一a>
<a onclick="doSomething()">点击二a>
  1. a 标签创建一个带有描述信息的href 属性,并监控click事件调用preventDefault()函数。
<a href="#Something_De scriptive" id="my_id">Triggera>
<script>
    $("#my_id").click(function(e){
        e.preventDefault(); //取消单击事件的默认动作以阻止链接的跳转。
        //  其他的代码
})
script>

优点:

gzip的组件

所有现代浏览器都支持 gzip 压缩并会为所有 HTTP 请求自动协商此类压缩。启用 gzip 压缩可大幅缩减所传输的响应的大小(最多可缩减 90%),从而显著缩短下载相应资源所需的时间、减少客户端的流量消耗并加快网页的首次呈现速度。

从HTTP / 1.1开始,Web客户端表示支持使用HTTP请求中的Accept-Encoding标头进行压缩。

Accept-Encoding:gzip,deflate

压缩包括XML和JSON在内的任何文本响应都是值得的。不应对图像和PDF文件进行gzip压缩,因为它们已经过压缩。试图对它们进行gzip不仅会浪费CPU,还可能会增加文件大小。

比如,在nginx中开启gzip压缩:

# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 2;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";

其他web容器启动gzip的方法:

  • Apache:使用 mod_deflate
  • Nginx:使用 ngx_http_gzip_module
  • IIS:配置 HTTP 压缩功能

CSS放在顶部,JS放在底部

把Javascript脚本在底部,删除阻止渲染的JavaScript
在HTML文件中指定外部样式表和内联样式块可能对浏览器的渲染性能产生不利影响。

  1. 浏览器阻塞渲染网页直到所有外部的样式表都已被下载。
  2. (用