Appearance
HTTP
对于前端性能优化我们经常说有下面几种方式:
- 减少 http 请求
- 压缩合并 js 以及 css、雪碧图
- 图片懒加载的技术
- 防止回流和重绘
- css 放头部、js 放底部。
那么对于 HTTP 方面我们优化的有那几点?
1. 减少 HTTP 请求
为什么要减少http的请求数?
- HTTP 请求建立和释放需要时间
每一个 HTTP 的请求都需要建立一个 TCP 的连接和关闭。HTTP1.0 的每一个连接都需要经历 TCP 三次握手、请求、响应、TCP 的四次挥手,到 HTTP1.1 的 keep-alive 长连接===========================================-=
- 浏览器对同一个域名的并发数量有限制
域名发散
2. 压缩合并 js 以及 css、雪碧图
这也是上面所说的 HTTP 请求的建立和释放都是需要时间的,如果页面的资源非常碎片化,每一个请求只有 1k 或者几 k,那么就非常损耗性能。
- 对于 js
我们可以通过 webpack 等构建时将引用次数较高的组件合并成一个通用包,那么在一开始就可以缓存这些数据减少其他页面的访问速度
但是我们对于组件的合并要有一个的规则,不然所有的都打包成一个 js,那么首页访问就会变得非常的缓慢,反而严重影响性能
对于 css 我们也是跟 js 的处理方式一样。
对于图片资源
一般都是使用雪碧图的方式,但是我们有些时候很多页面只需要显示其中的一两张图片的时候我们这时候我们要加载整个反而适得其反。所以对于图片我们应该有下面几种方式
雪碧图
小图片的 base64 内联嵌入
大图片的 DNS
注意:
webpack 打包中的 splitChunks, 一般需要设置 minChunks(最少引用次数)
webpack 中的 import() 动态加载模块
对于 PC 端可以进行 preload 和 prefetch(
移动端移除,流量问题
)prefetch : 资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低
preload : 本页面要用到的关键资源,包括关键 js、字体、css 文件。preload 将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度。
我们的静态资源服务器可以和数据接口服务器进行分片
数据接口服务器一般需要用的 cookie 数据进行用户的鉴权等,但是对于资源服务器我们就不依赖 cookie,那么我们就可以依赖 cookie 的同源策略使得在静态资源服务器访问不到当前域名下的 cookie 数据,那么就可以减少通信的大小
3. 图片懒加载的技术
在一个页面特别是企业官网、购物网站等类型的网站其依赖很多的图片,而且一个页面也高度也很高,但是图片资源是很大的,那么我们就可以在用户访问当前页面的时候首先加载可视区的图片,而对于可视区外的图片可以在用户滚动的时候进行加载
4. 防止回流和重绘
回流: 当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程。
重绘: 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它。
5. css 放头部、js 放底部
页面加载时间监控
页面的加载流程
浏览器输入 URL 并按下回车 -> 浏览器查找当前 URL 是否存在缓存 -> DNS 域名解析 -> 建立 TCP 连接 -> 浏览器发出取文件命令 -> 服务器响应 -> 释放 TCP 连接(4 次挥手) -> 服务器的永久重定向响应(可选) -> 解析 HTML-> 构建渲染树 -> 开始显示内容(白屏时间) -> 首屏内容加载完成(首屏时间) -> 用户可交互(DOMContentLoaded) -> 加载完成(load)
1. 浏览器输入 URL 并按下回车
没办法优化
2. 浏览器查找当前 URL 是否存在缓存
这一步主要涉及到 HTTP 缓存机制
所以对于静态资源我们需要通过 nginx 设置缓存
注意:
- 强缓存与弱缓存
- 缓存头部简述
- 头部的区别
3. DNS 域名解析
首先我们知道 我们一般输入的是一个域名,如 www.baidu.com,但是对于机器来说其需要的是一个 IP 地址,如 www.baidu.com === 192.16.0.0。 DNS 解析的过程就是将 域名解析成正确的 IP 地址数据。
浏览器解析域名的过程一般 首先其现在浏览器缓存中获取 www.baidu.com 的 IP 地址,如果你上次访问过此 IP 其一般会被浏览器缓存下来,那么这次就直接使用浏览器缓存的域名 IP 地址。
系统缓存(对应本地的 hosts 文件,windows 一般在 C:\Windows\System32\drivers\etc\hosts 这个文件)。 如果在浏览器缓存中找不到对应的数据,那么就在系统缓存中查询是否存在此域名 IP 地址。 所以我们在 hosts 中,将 www.baidu.com 127.0.0.1 就会发现 访问不到百度了。
本地 DNS 服务器(路由器) 本地 DNS 服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,如果没有其就会查询 ISP 服务器(运营商服务器-中国电信...)
下面是 DNS 根域名服务器 -> 顶级域名服务器 -> 二级域名服务器 这是一个 递归搜索 过程。
就是我们访问 www.baidu.com 的时候 DNS 会自动添加一个 . 在最后(www.baidu.com.) 。这时候 本地服务器就会请求根域名服务器,如果根域名服务器没有找到,这时候不是返回没有找到,而是返回顶级域的地址,让本地 DNS 服务器去此顶级域查询如 .com., 下一步就是 二级域名 baidu.com.。
所以上面的流程是 . -> com. -> baidu.com.
注意:
DNS 使用 udp,占 53 号端口
DNS 查询分为两种方式,递归查询,迭代查询
DNS 服务器分为四种 本地域名服务器、权威域名服务器、顶级域名服务器、根域名服务器
浏览器缓存:浏览器会按照一定的频率缓存 DNS 记录。
操作系统缓存:如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统中找。
路由缓存:路由器也有 DNS 缓存。
ISP 的 DNS 服务器:ISP 是互联网服务提供商(Internet Service Provider)的简称,ISP 有专门的 DNS 服务器应对 DNS 查询请求。
根服务器:ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询(DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址,然后再问.baidu 域名服务器,依次类推)
4. 服务器的永久重定向响应(可选)
有些时候服务器会给浏览器一个 301 的永久重定向响应,这样浏览器访问的 baidu.com 就重定向到 www.baidu.com。 这时候又会开启一个新的 从第二步开始的流程(DNS 预解析)
服务器重定向原因?
这个跟搜索引擎有关,如果官网更换一个新的地址,那么这时候官网就存在两个地址了,搜索引擎也就认为这是两个网站,那么这时候原来网站的排名、流量等都没用了。那么这时候通过 301 永久重定向就会将 新的地址 跟原来的地址归到同一个网站排名下。
5. 建立 TCP 连接 -> 浏览器发出取文件命令 -> 服务器响应 -> 释放 TCP 连接(4 次挥手)
注意:
- http 报文结构
- cookie 以及优化
- gzip 压缩
- 长连接与短连接
- http 2.0
- https
后台的处理
- 负载均衡 (nginx 有 ip hash, least_conn,还可以设置权重来实现负载均衡)
- 反向代理
5. 解析 HTML-> 构建渲染树 -> 开始显示内容(白屏时间) -> 首屏内容加载完成(首屏时间) -> 用户可交互(DOMContentLoaded) -> 加载完成(load)
- 浏览器显示 HTML
在浏览器没有完整接受全部 HTML 文档时,它就已经开始显示这个页面了,浏览器是如何把页面呈现在屏幕上的呢?不同浏览器可能解析的过程不太一样,这里我们只介绍 webkit 的渲染过程,下图对应的就是 WebKit 渲染的过程,这个过程包括:
解析 html 以构建 dom 树 -> 构建 render 树 -> 布局 render 树 -> 绘制 render 树
浏览器在解析 html 文件时,会”自上而下“加载,并在加载过程中进行解析渲染。在解析过程中,如果遇到请求外部资源时,如图片、外链的 CSS、iconfont 等,请求过程是异步的,并不会影响 html 文档进行加载。
解析过程中,浏览器首先会解析 HTML 文件构建 DOM 树,然后解析 CSS 文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和 repain(重绘)。
DOM 节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为 relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为 repain。
页面在首次加载时必然会经历 reflow 和 repain。reflow 和 repain 过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少 reflow 和 repain。
当文档加载过程中遇到 js 文件,html 文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中 js 文件加载完毕,还要等待解析执行完毕,才可以恢复 html 文档的渲染线程。因为 JS 有可能会修改 DOM,最为经典的 document.write,这意味着,在 JS 执行完成前,后续所有资源的下载可能是没有必要的,这是 js 阻塞后续资源下载的根本原因。所以我明平时的代码中,js 是放在 html 文档末尾的。
JS 的解析是由浏览器中的 JS 解析引擎完成的,比如谷歌的是 V8。JS 是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如 IO 读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。
JS 的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
浏览器渲染
客户机接受到二进制比特流之后,把比特流转换成帧格式,上传到数据链路层,客户机发现数据帧中的目的 MAC 地址与本网卡的 MAC 地址相同,拆除数据链路层的封装后,把数据包上传到网络层。网络层比较数据包中的目的 IP 地址,发现与本机的 IP 地址相同,拆除网络层的封装后,把数据分段上传到传输层。传输层对数据分段进行确认、排序、重组,确保数据传输的可靠性。数据最后被传到应用层
1、如果 HTTP 响应报文是 301 或 302 重定向,则浏览器会相应头中的 location 再次发送请求
2、浏览器处理 HTTP 响应报文中的主体内容,首先使用 loader 模块加载相应的资源
loader 模块有两条资源加载路径:主资源加载路径和派生资源加载路径。主资源即 google 主页的 index.html 文件 ,派生资源即 index.html 文件中用到的资源
主资源到达后,浏览器的 Parser 模块解析主资源的内容,生成派生资源对应的 DOM 结构,然后根据需求触发派生资源的加载流程。比如,在解析过程中,如果遇到 img 的起始标签,会创建相应的 image 元素 HTMLImageElement,接着依据 img 标签的内容设置 HTMLImageElement 的属性。在设置 src 属性时,会触发图片资源加载,发起加载资源请求
这里常见的优化点是对派生资源使用缓存
3、使用 parse 模块解析 HTML、CSS、Javascript 资源
【解析 HTML】
HTML 解析分为可以分为解码、分词、解析、建树四个步骤
(1)解码:将网络上接收到的经过编码的字节流,解码成 Unicode 字符
(2)分词:按照一定的切词规则,将 Unicode 字符流切成一个个的词语(Tokens)
(3)解析:根据词语的语义,创建相应的节点(Node)
(4)建树:将节点关联到一起,创建 DOM 树
【解析 CSS】
页面中所有的 CSS 由样式表 CSSStyleSheet 集合构成,而 CSSStyleSheet 是一系列 CSSRule 的集合,每一条 CSSRule 则由选择器 CSSStyleSelector 部分和声明 CSSStyleDeclaration 部分构成,而 CSSStyleDeclaration 是 CSS 属性和值的 Key-Value 集合
CSS 解析完毕后会进行 CSSRule 的匹配过程,即寻找满足每条 CSS 规则 Selector 部分的 HTML 元素,然后将其 Declaration 声明部分应用于该元素。实际的规则匹配过程会考虑到默认和继承的 CSS 属性、匹配的效率及规则的优先级等因素
【解析 JS】
JavaScript 一般由单独的脚本引擎解析执行,它的作用通常是动态地改变 DOM 树(比如为 DOM 节点添加事件响应处理函数),即根据时间(timer)或事件(event)映射一棵 DOM 树到另一棵 DOM 树
简单来说,经过了 Parser 模块的处理,浏览器把页面文本转换成了一棵节点带 CSS Style、会响应自定义事件的 Styled DOM 树
4、构建 DOM 树、Render 树及 RenderLayer 树
浏览器的解析过程就是将字节流形式的网页内容构建成 DOM 树、Render 树及 RenderLayer 树的过程
使用 parse 解析 HTML 的过程,已经完成了 DOM 树的构建,接下来构建 Render 树
【Render 树】
Render 树用于表示文档的可视信息,记录了文档中每个可视元素的布局及渲染方式
RenderObject 是 Render 树所有节点的基类,作用类似于 DOM 树的 Node 类。这个类存储了绘制页面可视元素所需要的样式及布局信息,RenderObject 对象及其子类都知道如何绘制自己。事实上绘制 Render 树的过程就是 RenderObject 按照一定顺序绘制自身的过程
DOM 树上的节点与 Render 树上的节点并不是一一对应的。只有 DOM 树的根节点及可视节点才会创建对应的 RenderObject 节点
【Render Layer 树】
Render Layer 树以层为节点组织文档的可视信息,网页上的每一层对应一个 Render Layer 对象。RenderLayer 树可以看作 Render 树的稀疏表示,每个 RenderLayer 树的节点都对应着一棵 Render 树的子树,这棵子树上所有 Render 节点都在网页的同一层显示
RenderLayer 树是基于 RenderObject 树构建的,满足一定条件的 RenderObject 才会建立对应的 RenderLayer 节点
下面是 RenderLayer 节点的创建条件:
(1)网页的 root 节点
(2)有显式的 CSS position 属性(relative,absolute,fixed) (3)元素设置了 transform
(4)元素是透明的,即 opacity 不等于 1
(5)节点有溢出(overflow)、alpha mask 或者反射(reflection)效果。
(6)元素有 CSS filter(滤镜)属性
(7)2D Canvas 或者 WebGL
(8)Video 元素
5、布局和渲染
布局就是安排和计算页面中每个元素大小位置等几何信息的过程。HTML 采用流式布局模型,基本的原则是页面元素在顺序遍历过程中依次按从左至右、从上至下的排列方式确定各自的位置区域
简单情况下,布局可以顺序遍历一次 Render 树完成,但也有需要迭代的情况。当祖先元素的大小位置依赖于后代元素或者互相依赖时,一次遍历就无法完成布局,如 Table 元素的宽高未明确指定而其下某一子元素 Tr 指定其高度为父 Table 高度的 30%的情况
Paint 模块负责将 Render 树映射成可视的图形,它会遍历 Render 树调用每个 Render 节点的绘制方法将其内容显示在一块画布或者位图上,并最终呈现在浏览器应用窗口中成为用户看到的实际页面
主要绘制顺序如下:
(1)背景颜色
(2)背景图片
(3)边框
(4)子呈现树节点
(5)轮廓
6、硬件加速
开启硬件渲染,即合成加速,会为需要单独绘制的每一层创建一个 GraphicsLayer
硬件渲染是指网页各层的合成是通过 GPU 完成的,它采用分块渲染的策略,分块渲染是指:网页内容被一组 Tile 覆盖,每块 Tile 对应一个独立的后端存储,当网页内容更新时,只更新内容有变化的 Tile。分块策略可以做到局部更新,渲染效率更高
一个 Render Layer 对象如果需要后端存储,它会创建一个 Render Layer Backing 对象,该对象负责 Renderlayer 对象所需要的各种存储。如果一个 Render Layer 对象可以创建后端存储,那么将该 RenderLayer 称为合成层(Compositing Layer)
如果一个 Render Layer 对象具有以下的特征之一,那么它就是合成层:
(1)RenderLayer 具有 CSS 3D 属性或者 CSS 透视效果。
(2)RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的视频解码技术的 HTML5 ”video”元素。
(3) RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的 Canvas2D 元素或者 WebGL 技术。
(4)RenderLayer 使用了 CSS 透明效果的动画或者 CSS 变换的动画。
(5)RenderLayer 使用了硬件加速的 CSSfilters 技术。
(6)RenderLayer 使用了剪裁(clip)或者反射(reflection)属性,并且它的后代中包括了一个合成层。
(7)RenderLayer 有一个 Z 坐标比自己小的兄弟节点,该节点是一个合成层
最终的渲染流程如下所示:
【重绘和回流】
重绘和回流是在页面渲染过程中非常重要的两个概念。页面生成以后,脚本操作、样式表变更,以及用户操作都可能触发重绘和回流
回流 reflow 是 firefox 里的术语,在 chrome 中称为重排 relayout
回流是指窗口尺寸被修改、发生滚动操作,或者元素位置相关属性被更新时会触发布局过程,在布局过程中要计算所有元素的位置信息。由于 HTML 使用的是流式布局,如果页面中的一个元素的尺寸发生了变化,则其后续的元素位置都要跟着发生变化,也就是重新进行流式布局的过程,所以被称之为回流
前面介绍过渲染引擎生成的 3 个树:DOM 树、Render 树、Render Layer 树。回流发生在 Render 树上。常说的脱离文档流,就是指脱离渲染树 Render Tree
重绘是指当与视觉相关的样式属性值被更新时会触发绘制过程,在绘制过程中要重新计算元素的视觉信息,使元素呈现新的外观
由于元素的重绘 repaint 只发生在渲染层 render layer 上。所以,如果要改变元素的视觉属性,最好让该元素成为一个独立的渲染层 render layer
下面列举一些减少回流次数的方法
(1)不要一条一条地修改 DOM 样式,而是修改 className 或者修改 style.cssText
(2)在内存中多次操作节点,完成后再添加到文档中去
(3)对于一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示
(4)在需要经常获取那些引起浏览器回流的属性值时,要缓存到变量中
(5)不要使用 table 布局,因为一个小改动可能会造成整个 table 重新布局。而且 table 渲染通常要 3 倍于同等元素时间
此外,将需要多次重绘的元素独立为 render layer 渲染层,如设置 absolute,可以减少重绘范围;对于一些进行动画的元素,可以进行硬件渲染,从而避免重绘和回流
- 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS 等等)