浏览器优化
# 浏览器的渲染机制
# 浏览器内核的理解
主要分成两部分:渲染引擎(layout engineer或Rendering Engine) 和 JS引擎
最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎
# 渲染引擎
负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。
# JS引擎
则解析和执行javascript来实现网页的动态效果。
# 浏览器的基础结构
一般我们把浏览器的基础结构分为一下七个部分:
- 用户界面(User lnterface): 即用户看到的及与之交互的功能组件,比如地址栏,返回,前进按钮等;
- 浏览器引擎(Browser engine):负责控制和管理下一级的渲染引擎;
- 渲染引擎(Rendering engine): 负责解析用户请求的内容(如HTML或XML,渲染引擎会解析HTML或XML,以及相关CSS,然后返回解析后的内容);
- (Networking):负责处理网络相关的事物,如HTTP请求等;
- UI后端(UI backend): 负责绘制提示框等浏览器组件,其底层使用的是操作系统的用户接口;
- JavaScript解释器(JavaScript interpreter): 负责解析和执行JavaScript代码;
- 数据存储(Data storage):负责持久存储如:cookie和缓存等应用数据。
# 浏览器及其内核
Chrome | Firefox | Safari | Opera | IE,TT, 360 | |
---|---|---|---|---|---|
内核 | 以前Webkit,现Blink(WebKit分支) | Gecko(俗称Firefox内核) | Webkit | 最初自己的Presto,后Webkit,现Blink | Trident(俗称IE内核) |
JS 引擎 | V8 | SpiderMonkey | Nitro | V8 | Chakra |
国产浏览器:
360浏览器、猎豹浏览器内核
:IE+Chrome双内核;
搜狗、遨游、QQ浏览器内核
:Trident(兼容模式)+Webkit(高速模式);
百度浏览器、世界之窗内核
:IE内核;
2345浏览器内核
:以前是IE内核,现在也是IE+Chrome双内核;
# 整个浏览器工作的流程
- 输入网址。
- 浏览器查找域名的IP地址。
- 浏览器给web服务器发送一个HTTP请求
- 网站服务的永久重定向响应
- 浏览器跟踪重定向地址 现在,浏览器知道了要访问的正确地址,所以它会发送另一个获取请求。
- 服务器“处理”请求,服务器接收到获取请求,然后处理并返回一个响应。
- 服务器发回一个HTML响应
- 浏览器开始显示HTML
- 浏览器发送请求,以获取嵌入在HTML中的对象。在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。这些文件就包括CSS/JS/图片等资源,这些资源的地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等…
要点如下:
- 浏览器根据 DNS 服务器得到域名的 IP 地址
- 向这个 IP 的机器发送 HTTP 请求
- 服务器收到、处理并返回 HTTP 请求
- 浏览器得到返回内容
# 浏览器的渲染机制
# 几个基本概念
DOM:Document Object Model,浏览器将HTML解析成树形的数据结构,简称DOM。
CSSOM:CSS Object Model,浏览器将CSS解析成树形的数据结构,简称CSSOM。
Render Tree: DOM和CSSOM合并后生成Render Tree。如下图
Layout: 计算出Render Tree每个节点的具体位置。
Painting:通过显卡,将Layout后的节点内容分别呈现到屏幕上。
![](~/12-28-32.jpg)
# 渲染步骤
以Webkit内核浏览器为例,浏览器的渲染可以分为以下几步:【DCJ渲布绘】
- 1.构建DOM树(DOM tree):在刚开始渲染时,浏览器会自上而下解析HTML文档生成DOM节点树,也叫做内容树(content tree);
- 2.加载解析样式,构建CSSOM树(CSS Object Model tree);
- 3.加载并执行JavaScript代码;
- 4.根据DOM树和CSSOM树,生成渲染树(render tree)
- 5.布局(layout/reflow):根据渲染树将节点树的每一个节点布局在屏幕的正确位置;
- 6.绘制(painting/repaint):遍历渲染树绘制的所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成的。; 遍历渲染树,使用UI后端层来绘制每个节点。
Gecko浏览器和Webkit浏览器渲染流程大致相同,不同的是:
- 1.Webkit浏览器中的渲染树(render tree),在Gecko浏览器中对应的则是框架树(frame tree),渲染对象(render object)对应的是框架(frame);
- 2.Webkit中的布局(Layout)过程,在Gecko中称为回流(Reflow),本质是一样的,后文会解释回流的另一层含义–重新布局;
- 3.Gecko中HTML和DOM树中间多了一层内容池(Content sink),可以理解成生成DOM元素的工厂。
图示比较:参考文档 (opens new window) ![](~/15-07-38.jpg) ![](~/15-07-56.jpg) ![](~/16-07-36.jpg)
# 浏览器解析渲染页面
浏览器拿到响应文本 HTML 后,接下来介绍下浏览器渲染机制
浏览器解析渲染页面分为一下五个步骤:【DCJ渲布绘】
- 根据 HTML 解析出 DOM 树
- 根据 CSS 解析生成 CSS 规则树
- 结合 DOM 树和 CSS 规则树,生成渲染树
- 根据渲染树计算每一个节点的信息
- 根据计算好的信息绘制页面
# 1.根据 HTML 解析 DOM 树
- 根据 HTML 的内容,将标签按照结构解析成为 DOM 树,DOM 树解析的过程是一个深度优先遍历。即先构建当前节点的所有子节点,再构建下一个兄弟节点。
- 在读取 HTML 文档,构建 DOM 树的过程中,若遇到 script 标签,则 DOM 树的构建会暂停,直至脚本执行完毕。
# 2.根据 CSS 解析生成 CSS 规则树
- 解析 CSS 规则树时 js 执行将暂停,直至 CSS 规则树就绪。
- 浏览器在 CSS 规则树生成之前不会进行渲染。
# 3.结合 DOM 树和 CSS 规则树,生成渲染树
- DOM 树和 CSS 规则树全部准备好了以后,浏览器才会开始构建渲染树。
- 精简 CSS 并可以加快 CSS 规则树的构建,从而加快页面相应速度。
# 4.根据渲染树计算每一个节点的信息(布局)
- 布局:通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸
- 回流:在布局完成后,发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。
# 5.根据计算好的信息绘制页面
- 绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。
- 重绘:某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的重绘。
- 回流:某个元素的尺寸发生了变化,则需重新计算渲染树,重新渲染。
# 重绘(Repaint)和回流(Reflow)
重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。
- 重绘是当节点需要更改外观而不会影响布局的,比如改变
color
就叫称为重绘 - 回流是布局或者几何属性需要改变就称为回流。
# 总括
下面是渲染引擎在取得内容之后的基本流程:(4步骤)
解析html 以构建dom树【DOM tree】+cssom树【CSSOM tree】 -> 构建render树【render tree】 -> 布局render树【layout】 -> 绘制render树【painting】
浏览器渲染过程大体分为如下三部分:
浏览器会解析三个东西:
- HTML/SVG/XHTML,解析这三种文件会产生一个 DOM Tree。
- CSS,解析 CSS 会产生 CSS 规则树。
- Javascript脚本,主要是通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree.
解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。
- Rendering Tree 渲染树并不等同于DOM树,渲染树只会包括需要显示的节点和这些节点的样式信息。
- CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加到Rendering Tree上的每个Element(也就是每个Frame)。
- 然后,计算每个Frame 的位置,这又叫layout和reflow过程。
最后通过调用操作系统Native GUI的API绘制
# 资源加载顺序
当我们浏览器获得HTML文件后,会自上而下的加载,并在加载过程中进行解析和渲染。
加载说的就是获取资源文件的过程,如果在加载过程中遇到外部CSS文件和图片,浏览器会另外发送一个请求,去获取
CSS文件和相应的图片
,这个请求是异步的,并不会影响HTML文件的加载。但是如果遇到Javascript文件,HTML文件会
挂起渲染的进程
,等待JavaScript文件加载完毕后,再继续进行渲染。
为什么HTML需要等待JavaScript呢?
因为JavaScript可能会修改DOM,导致后续HTML资源空白白加载,所以HTML必须等待JavaScript文件加载完毕后,再继续渲染,这也就是为什么JavaScript文件在写在底部body标签前的原因。
把脚本放到页面顶部会导致明显的延迟,通常表现为显示空白页面,用户无法浏览内容,也无法和页面进行交互。
# 渲染相关性能优化
了解浏览器如何进行加载,我们可以在引用外部样式文件,外部JS时,将它们放到合适的位置,是浏览器以最快的速度,将文件加载完毕。
了解浏览器如何进行解析,我们可以在构建DOM结构,组织CSS选择器的时候,选择最优的写法,提高浏览器的解析速率。
了解浏览器如何进行渲染,明白渲染的过程,我们在设置元素属性,编写JS文件时,可以减少“重绘”,“重新布局”的消耗。
# Repaint(重绘)与Reflow(回流)的比较区别
Repaint ——改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
- 常见引起重绘属性和方法:改变元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性
![](~/16-15-21.jpg)
- 常见引起重绘属性和方法:改变元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性
Reflow ——元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。 **Reflow要比Repaint更花费时间,也就更影响性能。**所以在写代码的时候,要尽量避免过多的Reflow。
- 常见引起回流属性和方法: 任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流,
- 页面初始化的时候; (无法避免)
- 如果 CSS 的属性发生变化;
- 添加或者删除可见的DOM元素;操作DOM时;
- 元素尺寸改变——边距、填充、边框、宽度和高度;
- 内容变化,比如用户在input框中输入文字
- 浏览器窗口尺寸改变——resize事件发生时
- 计算 offsetWidth 和 offsetHeight 属性
- 设置 style 属性的值
引起重绘和回流的操作如下:
- 添加、删除元素(回流+重绘)
- 隐藏元素,display: none(回流+重绘),visibility:hidden(只重绘,不回流);display:none会触发reflow+repaint,而visibility:hidden只会触发repaint,因为没有发现位置变化。
- 移动元素,比如改变 top、left 的值,或者移动元素到另外一个父元素中。(重绘+回流)
- 对 style 的操作(对不同的属性操作,影响不一样)
- 还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
- 常见引起回流属性和方法: 任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流,
注意问题:
transform 操作不会引起重绘和回流,是一种高效率的渲染。这是因为transform属于合成属性,对合成属性进行transition/animation 动画时将会创建一个合成层,这使得动画元素在一个独立的层中进行渲染,当元素的内容没有发生改变,就没必要进行重绘,浏览器会通过重新复合来创建动画帧。
# display: none与visibility: hidden的区别
visibility: hidden和display: none的区别仅仅在于display: none隐藏后的元素不占据任何空间,而visibility: hidden隐藏后的元素空间依旧保留 ,实际上没那么简单,visibility是一个非常有故事性的属性
1、visibility具有继承性,给父元素设置visibility:hidden;子元素也会继承这个属性。但是如果重新给子元素设置visibility: visible,则子元素又会显示出来。这个和display: none有着质的区别
2、visibility: hidden不会影响计数器的计数,如图所示,visibility: hidden虽然让一个元素不见了,但是其计数器仍在运行。这和display: none完全不一样
3、CSS3的transition支持visibility属性,但是并不支持display,由于transition可以延迟执行,因此可以配合visibility使用纯css实现hover延时显示效果。提高用户体验。
# 减少 reflow/repaint 回流、重绘
为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。
使用 transform 替代 top
使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
CSS 选择符从右往左匹配查找,避免节点层级过多;
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。
不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className。
不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
for(let i = 0; i < 1000; i++) {// 获取 offsetTop 会导致回流,因为需要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop) }
1
2
3
# 性能优化策略
基于上面介绍的浏览器渲染原理,DOM 和 CSSOM 结构构建顺序,初始化可对页面渲染做些优化,提升页面性能。
- CSS优化:
<link>
标签的 rel属性中的属性值设置为preload能够让你在你的HTML页面中可以指明哪些资源是在页面加载完成后即刻需要的,最优的配置加载顺序,提高渲染性能 - JS优化:
<script>
标签加上 defer属性 和 async属性用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。- defer属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。延迟加载【推荐】
- async属性: HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。异步加载
# 写css的时候需要注意
- dom深度尽量浅。
- 减少inline javascript、css的数量。
- 使用现代合法的css属性。
- 不要为id选择器指定类名或是标签,因为id可以唯一确定一个元素。
- 避免后代选择符,尽量使用子选择符。原因:子元素匹配符的概率要大于后代元素匹配符。后代选择符;#tp p{} 子选择符:#tp>p{}
- 避免使用通配符,举一个例子,.mod .hd *{font-size:14px;} 根据匹配顺序,将首先匹配通配符,也就是说先匹配出通配符,然后匹配.hd(就是要对dom树上的所有节点进行遍历他的父级元素),然后匹配.mod,这样的性能耗费可想而知.
# 减少js对性能的影响的方法
- 将所有的script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
- 尽可能地合并脚本。页面中的script标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。
- 采用无阻塞下载 JavaScript 脚本的方法: (1)使用script标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本); (2)使用动态创建的script元素来下载并执行代码;
# 性能优化
通过优化从而提高页面的加载速度。
# 缓存方面优化
# 使用CDN,减少资源下载时间
CDN内容分发网络,用户访问一个URL的时候,CDN会根据用户所在区域,访问的内容和服务器的负载情况,返回一台缓存服务器的IP地址给用户访问;
浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率 。
静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。
常用CDN公共库:总结:如果网站面向国内用户推荐使用 BootCDN,面向国际用户则使用 jsDelivr。
BootCDN(opens new window)(跳转到 cdnjs)推荐- jsDelivr (opens new window) (Multi-CDN, 有国内节点 (opens new window)) 推荐
- unpkg (opens new window) (by Heroku & CloudFlare) 推荐
- 开放静态文件 CDN - 七牛 (opens new window) 推荐
- 75CDN 前端静态资源库 (opens new window)
- 今日头条静态资源公共库 (opens new window)
小提示:如果你使用 CDN 服务,建议准备一个备胎,万一 CDN 服务挂了,可以从自己的服务器上读取:
<script src="//cdn.staticfile.org/jquery/2.0.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="/jquery.min.js"><\/script>')</script>
2
使用实例:
//unpkg.com/:package@:version/:file
https://unpkg.com/jquery@3.4.1/dist/jquery.js
https://unpkg.com/react@16.7.0/umd/react.production.min.js
https://unpkg.com/react/umd/react.production.min.js
2
3
4
# 根据http缓存
# 按协议分
协议层缓存和非 http 协议缓存
非协议层缓存:利用 meta
标签的 http-equiv
属性值 Expires
,set-cookie
。
协议层缓存:利用 http 协议头属性值设置;
# 按缓存分
强缓存和协商缓存
强缓存:利用 cache-control 和 expires 设置,直接返回一个过期时间,所以在缓存期间不请求;
协商缓存:响应头返回 etag 或 last-modified 的哈希值,第二次请求头 If-none-match 或 IF-modify-since 携带上次哈希值,一致则返回 304。
添加Expires头:可以最大化地利用浏览器的缓存能力来改善页面的性能。通过使用一个长久的Expires头,使组件被缓存,可以在后续的页面浏览中避免不必要的http请求。 web服务器使用Expires头来告诉web客户端它可以使用一个组件的当前副本,直到指定时间为止。
Cache-Control:Expires头有一定的限制,就是要求服务器和客户端的时钟严格同步,另外,过期日期需要经常检查。HTTP 1.1引入了Cache-Control头来克服Expires头的限制。Cache-Control可以重写Expires头。
对于强制缓存,服务器响应的header中会用两个字段来表明——Expires和Cache-Control。
# Expires
Exprires的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差,另一方面,Expires是HTTP1.0的产物,故现在大多数使用Cache-Control替代。
# Cache-Control
Cache-Control有很多属性,不同的属性代表的意义也不同。
private:客户端可以缓存
public:客户端和代理服务器都可以缓存
max-age=t:缓存内容将在t秒后失效
no-cache:需要使用协商缓存来验证缓存数据
no-store:所有内容都不会缓存。
# 资源文件方面优化
# HTML 优化
- 避免 HTML 中书写 CSS 代码,因为这样难以维护。
- 使用 Viewport 加速页面的渲染。
- 使用语义化标签,减少 CSS 代码,增加可读性和 SEO。
- 减少标签的使用,DOM 解析是一个大量遍历的过程,减少不必要的标签,能降低遍历的次数。
- 避免 src、href 等的值为空,因为即时它们为空,浏览器也会发起 HTTP 请求。
- 减少 DNS 查询的次数。
- 减少iframe负担;iframe会阻止页面的加载,而且即使iframe内容为空也会消耗时间,所以尽量避免使用;
# CSS 优化
- 优化选择器路径:使用
.c {}
而不是.a .b .c {}
。 - 选择器合并:共同的属性内容提起出来,压缩空间和资源开销。
- 精准样式:使用
padding-left: 10px
而不是padding: 0 0 0 10px
。 - 雪碧图:将小的图标合并到一张图中,这样所有的图片只需要请求一次。
- 避免通配符:
.a .b * {}
这样的选择器,根据从右到左的解析顺序在解析过程中遇到通配符* {}
会遍历整个 DOM,性能大大损耗。 - 少用 float:
float
在渲染时计算量比较大,可以使用 flex 布局。 - 为 0 值去单位:增加兼容性。
- 压缩文件大小,减少资源下载负担。
- 用link代替@import:页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载; @import相当于将css放在网页内容底部。
# JavaScript 优化
- 尽可能把
<script>
标签放在body
之后,避免 JS 的执行卡住 DOM 的渲染,最大程度保证页面尽快地展示出来。 - 尽可能合并 JS 代码:提取公共方法,进行面向对象设计等……
- CSS 能做的事情,尽量不用 JS 来做,毕竟 JS 的解析执行比较粗暴,而 CSS 效率更高。
- 尽可能少地创建 DOM,而是在 HTML 和 CSS 中使用
display: none
来隐藏,按需显示 。其实应该用visibility:hidden; - 压缩文件大小,减少资源下载负担。
- 尽可能逐条操作 DOM,并预定好 CSS 样式,从而减少 reflow 或者 repaint 的次数。
- 减少重排与重绘:先让元素脱离文档流,处理完毕后再让元素回归文档流,这样浏览器只会进行两次重排与重绘(脱离时和回归时)
- 减少DOM元素数量:网页中元素过多对网页的加载和脚本的执行都是沉重的负担,500个元素和5000个元素在加载速度上会有很大差别。所以减少DOM元素数量是十分有必要的。
- 减少DOM操作:通过js访问DOM元素没有想象中快,元素多的网页尤其慢,利用js对DOM的访问时要注意:
- 缓存已经访问过的元素;把DOM集合的长度缓存到变量中并在迭代中使用。读变量比读DOM的速度要快;
- Offline更新节点然后再加回DOM Tree
- 避免通过Javascript修复layout
# 图片优化
# 计算图片大小
对于一张 100 * 100 像素的图片来说,图像上有 10000 个像素点,如果每个像素的值是 RGBA 存储的话,那么也就是说每个像素有 4 个通道,每个通道 1 个字节(8 位 = 1个字节),所以该图片大小大概为 39KB(10000 * 1 * 4 / 1024)。
但是在实际项目中,一张图片可能并不需要使用那么多颜色去显示,我们可以通过减少每个像素的调色板来相应缩小图片的大小。了解了如何计算图片大小的知识,那么对于如何优化图片,想必大家已经有 2 个思路了:
- 减少像素点
- 减少每个像素点能够显示的颜色
# 图片加载优化
- 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
- 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
- 小图使用 base64 格式;
- 将多个图标文件整合到一张图片中**(雪碧图);将多个图片合并到一个单独的图片中。如果用作背景图片,可以根据background-position来定位背景**,这样需要很多张背景图片现在就只需要一张了。也就是本来需要发送多次http请求来请求图片现在只需要发送一次就可以了。
- 选择正确的图片格式:
- 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
- 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
- 照片使用 JPEG
- 内联图片 ;通过编码的字符串将图片内嵌到网页文本中。通过使用data: URL模式可以在web页面中包含图片但无需额外的http请求。
# 网络加载方面优化
服务端开启文件压缩功能
- 启用GZIP压缩;保持良好的编程习惯,避免重复的CSS,JavaScript代码,多余的HTML标签和属性;
将
script
标签放在body
底部,因为 JS 文件执行会阻塞渲染。- 当然也可以把
script
标签放在任意位置然后加上defer
,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。 - 对于没有任何依赖的 JS 文件可以加上
async
,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。
- 当然也可以把
执行 JS 代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑使用
Webworker
。Webworker
可以让我们另开一个线程执行脚本而不影响渲染。使用多域名及使用内容发布网络(CDN);浏览器一般会限制每个域的并行线程(一般为 6 个,甚至更少),使用不同的域名可以最大化下载线程,但注意保持在
2-4 个域名
内,以避免 DNS 查询损耗。压缩大小;合并js、css文件,图片做成雪碧图,减少请求;
延迟(懒)加载:需要知道网页最初加载需要的最小内容集是什么。剩下的内容就可以延迟加载了。
提前加载:与延迟加载目的相反,提前加载的是为了提前加载接下来网页中要访问的资源。
icon类可以使用iconfont或者SVG
减少http请求;http请求需要进行三次握手,这是很费时间的。通过查看流量也可以发现,大部分的时间都花在了为html文档所引用的所有组件进行的http请求上。因此减少http请求可以提高网站性能。
合并脚本和样式表;将多个js脚本文件打包成一个文件,将多个css样式表打包成一个样式表。如果分成多个小文件,每个文件都会导致一个额外的http请求。
压缩组件:压缩组件可以减小http响应的大小,进而减少响应时间。如果http请求产生的响应包很小,传输时间就会减少。
将样式表放在顶部:将样式表放在文档底部会导致在浏览器中阻止内容逐步呈现**,将样式表放在顶部会显得加载速度更快**,这样可以是浏览器逐步呈现已经下载的网页内容。对于内容比较多的网页尤其重要,用户不用一直等待一个白屏的页面,而是可以先看已经下载的内容。CSS 文件放在
head
中将脚本放在底部:在使用样式表时,页面逐步呈现会被阻止,直到所有的样式表下载完成。将样式表移到head中,就能首先下载样式表而不会阻止页面呈现。使用脚本时,所有位于脚本以下的内容,逐步呈现都会被阻塞,将脚本放在页面越靠下的位置,意味着越多的内容能够逐步地呈现。
避免CSS表达式:css表达式的问题在于对其进行求值的频率比我们期望的要高。不只是在页面呈现和大小改变时求值,当页面滚动、甚至用户鼠标在页面上移过时都要求值。
使用外部JavaScript和CSS:
减少DNS查找:Internet是通过ip地址来查找服务器的,由于IP地址很难记忆,通常使用url代替,但是当浏览器发送http请求时还是需要ip地址,DNS就是用来将url映射到ip地址上的。DNS查找当然也是有开销的,通常要花费20-120毫秒。在DNS查找完成之前,浏览器不能从主机名那下载到任何内容。
DNS缓存:DNS查找可以被缓存起来以提高查找性能,通常浏览器和用户主机都会进行DNS缓存。
减少DNS查找:DNS查找的数量与页面中唯一主机名的数量相等,包括页面url、图片、脚本文件、样式表、Flash对象等的主机名。减少唯一主机名的数量就可以减少DNS查找的数量。
精简JavaScript:精简,就是从代码中移除不必要的字符以减小其大小,进而改善页面加载时间。同样,css文件也可以进行精简。压缩同时也可以减小所需要请求文件的大小,进而加快文件的请求。
避免重定向:重定向是将用户从一个url重新路由到另一个url,重定向会使页面加载变慢。
移除重复脚本:在一个页面中两次包含同样的一个js文件会损伤性能。一个大型的网站可能是多人协作或者多团队协作的,这样脚本被重复添加是很可能发生的事情。重复的脚本会增加不必要的http请求和浪费执行js所用的时间,这样会损伤页面性能,所以需要避免使用同样的脚本。
# 页面重构操作优化
# 网站重构
在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。 也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。
对于传统的网站来说重构通常是:
- 表格(table)布局改为DIV+CSS;
- 为什么不使用表格布局
- 更多的标签,增加文件大小;
- 不易维护,无法适应响应式设计;
- 性能考量,默认的表格布局算法会产生大量重绘
- 为什么不使用表格布局
- 使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)
- 对于移动平台的优化
- 针对于SEO进行优化
- 深层次的网站重构应该考虑的方面
- 减少代码间的耦合
- 让代码保持弹性
- 严格按规范编写代码
- 设计可扩展的API
- 代替旧有的框架、语言(如VB)
- 增强用户体验
- 通常来说对于速度的优化也包含在重构中
- 压缩JS、CSS、image等前端资源(通常是由服务器来解决)
- 程序的性能优化(如数据读写)
- 采用CDN来加速资源加载
- 对于JS DOM的优化
- HTTP服务器的文件缓存
# 性能监控
可以使用第三方工具。浏览器也有相应api performace.timing
- 白屏时间:responseStart - navigationStart
- 首屏时间:imgLoadTime(自己计算的,api没有) - navigationStart
- 用户可操作时间:domContentLoadedEventEnd - navigationStart;
- Dom加载完成时间:loadEventEnd - navigationStart
# 查找性能瓶颈
Chrome 的 Performance 面板可以录制一段时间内的 js 执行细节及时间。使用 Chrome 开发者工具分析页面性能的步骤如下。
- 打开 Chrome 开发者工具,切换到 Performance 面板
- 点击 Record 开始录制
- 刷新页面或展开某个节点
- 点击 Stop 停止录制
# 简要性能优化的方法
- 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。
- 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
- 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
- 当需要设置的样式很多时设置className而不是直接操作style。
- 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。
- 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。
- 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
- 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。
# 雅虎性能优化【推荐】
# 页面内容
减少 HTTP 请求数
- 合并 JavaScript、CSS 等文件;
- 使用CSS Sprite (opens new window):将背景图片合并成一个文件,通过
background-image
和background-position
控制显示; - Image Map (opens new window):合并图片,然后使用坐标映射不同的区域
- Inline Assets:使用 Data URI scheme (opens new window) 将图片嵌入 HTML 或者 CSS 中;或者将 CSS、JS、图片直接嵌入 HTML 中; 未来的趋势是使用内嵌 SVG。
- 内容分片 (opens new window),将请求划分到不同的域名上。
减少 DNS 查询
首次访问、没有相应的 DNS 缓存时,域名越多,查询时间越长。所以应尽量减少域名数量。但基于并行下载考虑,把资源分布到 2 个域名上(最多不超过 4 个)。这是减少 DNS 查询同时保证并行下载的折衷方案。
预解析DNS;
<meta http-equiv="x-dns-prefetch-control" content="no"> <link rel="dns-prefetch" href="//host_name_to_prefetch.com">
1
2
避免重定向
- 客户端收到服务器的重定向响应后,会根据响应头中
Location
的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。 - 有时重定向无法避免,在糟糕也比抛出 404 好。虽然通过 HTML meta refresh (opens new window) 和 JavaScript 也能实现,但首选 HTTP
3xx
跳转,以保证浏览器「后退」功能正常工作(也利于 SEO)。- 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加
/
但未添加。 - 网站域名变更:CNAME 结合
Alias
或mod_rewrite
或者其他服务器类似功能实现跳转。
- 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加
- 客户端收到服务器的重定向响应后,会根据响应头中
缓存 Ajax 请求
- Ajax 可以提高用户体验。但「异步」不意味着「及时」,优化 Ajax 响应速度提高性能仍是需要关注的主题。
- 最重要的的优化方式是缓存响应结果,详见 [添加 Expires 或 Cache-Control 响应头]
延迟加载
预先加载
减少 DOM 元素数量
- 是否还在使用表格布局? 性能考量,默认的表格布局算法会产生大量重绘
- 塞进去更多的
<div>
仅为了处理布局问题?也许有更好、更语义化的标记。 - 能通过伪元素实现的功能,就没必要添加额外元素,如清除浮动。
划分内容到不同域名
尽量减少 iframe 使用
避免 404 错误
- HTTP 请求很昂贵,返回无效的响应(如 404 未找到)完全没必要,降低用户体验而且毫无益处。
定义字符集
- 定义字符集,并放在
<head>
顶部。大多数浏览器会暂停页面渲染,直到找到字符集定义。
- 定义字符集,并放在
# 服务器
- 参考配置: 服务器相关优化设置可参考 H5BP 相关项目:
- 使用 CDN
- 网站 80-90% 响应时间消耗在资源下载上,减少资源下载时间是性能优化的黄金发则。
- 添加 Expires 或 Cache-Control 响应头
- 静态内容:将
Expires
响应头设置为将来很远的时间,实现「永不过期」策略; - 动态内容:设置合适的
Cache-Control
响应头,让浏览器有条件地发起请求。
- 静态内容:将
- 启用 Gzip;
gzip
使用了LZ77
算法与Huffman
编码来压缩文件,重复度越高的文件可压缩的空间就越大。【gzip 的原理】- Gzip 压缩通常可以减少 70% 的响应大小,对某些文件更可能高达 90%,比 Deflate 更高效。应该对 HTML、CSS、JS、XML、JSON 等文本类型的内容启用压缩。
- 注意,图片和 PDF 文件不要使用 gzip。它们本身已经压缩过,再使用 gzip 压缩不仅浪费 CPU 资源,而且还可能增加文件体积。
- 对于不支持的 Gzip 的用户代理,通过设置 Vary 响应头,返回为未压缩的数据:
Vary: *
- 配置 Etag
- Etag 通过文件版本标识,方便服务器判断请求的内容是否有更新,如果没有就响应
304
,避免重新下载。
- Etag 通过文件版本标识,方便服务器判断请求的内容是否有更新,如果没有就响应
- 尽早输出缓冲
- Ajax 请求使用 GET 方法
- 浏览器执行 XMLHttpRequest POST 请求时分成两步,先发送 Header,再发送数据。而 GET 只使用一个 TCP 数据包发送数据,所以首选 GET 方法。
- 避免图片 src 为空
- 空
src
请求的后果不容小觑:<img src="" /> var img = new Image(); img.src = "";
- 给服务器造成意外的流量负担,尤其时日 PV 较大时;
- 浪费服务器计算资源;
- 可能产生报错。
- 空
# Cookie
- 减少 Cookie 大小
- Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。
- 去除不必要的 Cookie;
- 尽量压缩 Cookie 大小;
- 注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;
- 设置合适的过期时间。
- Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。
- 静态资源使用无 Cookie 域名
- 静态资源一般无需使用 Cookie,可以把它们放在使用二级域名或者专门域名的无 Cookie 服务器上,降低 Cookie 传送的造成的流量浪费,提高响应速度。
# CSS
- 把样式表放在
<head>
中- 如果把样式表放在页面底部,一些浏览器为减少重绘,会在 CSS 加载完成以后才渲染页面,用户只能对着白屏干瞪眼,用户体验极差。
- 不要使用 CSS 表达式
- CSS 表达式超出预期的频繁执行,页面滚动、鼠标移动时都会不断执行,带来很大的性能损耗。
- 使用
<link>
替代@import
- 对于 IE 某些版本,
@import
的行为和 `` 放在页面底部一样。所以,不要用它。
- 对于 IE 某些版本,
- 不要使用 filter
AlphaImageLoader
为 IE5.5-IE8 专有的技术,和 CSS 表达式一样,放进博物馆吧。注意: 这里所说的不是 CSS3 Filter (opens new window),
# JavaScript
把脚本放在页面底部
- 浏览器下载脚本时,会阻塞其他资源并行下载,即使是来自不同域名的资源。因此,最好将脚本放在底部,以提高页面加载速度。 一些特殊场景无法将脚本放到页面底部的,可以考虑
<script>
的以下属性:
- 浏览器下载脚本时,会阻塞其他资源并行下载,即使是来自不同域名的资源。因此,最好将脚本放在底部,以提高页面加载速度。 一些特殊场景无法将脚本放到页面底部的,可以考虑
使用外部 JavaScript 和 CSS
- 外部 JavaScript 和 CSS 文件可以被浏览器缓存,在不同页面间重用,也能降低页面大小。
- 当然,实际中也需要考虑代码的重用程度。如果仅仅是某个页面使用到的代码,可以考虑内嵌在页面中,减少 HTTP 请求数。另外,可以在首页加载完成以后,预先加载子页面的资源。
压缩 JavaScript 和 CSS
- 压缩代码可以移除非功能性的字符(注释、空格、空行等),减少文件大小,提高载入速度。Gulp、Webpack 等流行构建工具的配套版本
移除重复脚本
减少 DOM 操作
- 缓存已经访问过的元素;
- 使用 DocumentFragment (opens new window) 暂存 DOM,整理好以后再插入 DOM 树;
- 操作 className,而不是多次读写
style
; - 避免使用 JavaScript 修复布局。
使用高效的事件处理
- 减少绑定事件监听的节点,如通过事件委托;
- 尽早处理事件,在
DOMContentLoaded
即可进行,不用等到load
以后。
对于
resize
、scroll
等触发频率极高的事件,应该通过 debounce 等机制降低处理程序执行频率。
# 图片
优化图片
- PNG 终极优化;
- Webp 相关内容;
- SVG 相关内容。
优化 CSS Sprite
- 水平排列 Sprite 中的图片,垂直排列会增加图片大小;
- Spirite 中把颜色较近的组合在一起可以降低颜色数,理想状况是低于 256 色以适用 PNG8 格式;
- 不要在 Spirite 的图像中间留有较大空隙。减少空隙虽然不太影响文件大小,但可以降低用户代理把图片解压为像素图的内存消耗,对移动设备更友好。
不要在 HTML 中缩放图片
不要使用
<img>
的width
、height
缩放图片,如果用到小图片,就使用相应大小的图片。很多 CMS 和 CDN 都提供图片裁切功能。
使用体积小、可缓存的 favicon.ico
Favicon.ico 一般存放在网站根目录下,无论是否在页面中设置,浏览器都会尝试请求这个文件。
所以确保这个图标:
- 存在(避免 404);
- 尽量小,最好小于 1K;
- 设置较长的过期时间。
对于较新的浏览器,可以使用 PNG 格式的 favicon。
设置图片的宽和高
- 以免浏览器按照「猜」的宽高给图片保留的区域和实际宽高差异,产生重绘。
# 移动端
- 保持单个文件小于 25 KB
- 打包内容为分段(multipart)文档
# 其他相关
# 懒加载和预加载的区别
图片的懒加载和预加载
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。 懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
# 减少页面加载时间的方法
- 服务器开启gzip压缩; CDN(Content Delivery Network)网络加速;
- css样式的定义放置在文件头部;
- Javascript脚本放在文件末尾;
- 压缩合并Javascript、CSS代码;
- 尽量减少页面中重复的HTTP请求数量; 【减少Http的请求】(合并文件,合并图片)
- 使用多域名负载网页内的多个文件、图片;
- 网址后面加上“/”:对服务器而言,不加斜杠服务器会多一次判断的过程,加斜杠就会直接返回网站设置的存放在网站根目录下的默认页面。
# 参考链接
https://blog.csdn.net/XIAOZHUXMEN/article/details/52014901
https://csspod.com/frontend-performance-best-practices
https://developer.yahoo.com/performance/rules.html