浏览器优化

# 浏览器的渲染机制

# 浏览器内核的理解

主要分成两部分:渲染引擎(layout engineer或Rendering Engine) 和 JS引擎

最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎

# 渲染引擎

负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。

# JS引擎

则解析和执行javascript来实现网页的动态效果。

# 浏览器的基础结构

一般我们把浏览器的基础结构分为一下七个部分:

  1. 用户界面(User lnterface): 即用户看到的及与之交互的功能组件,比如地址栏,返回,前进按钮等;
  2. 浏览器引擎(Browser engine):负责控制和管理下一级的渲染引擎;
  3. 渲染引擎(Rendering engine): 负责解析用户请求的内容(如HTML或XML,渲染引擎会解析HTML或XML,以及相关CSS,然后返回解析后的内容);
  4. (Networking):负责处理网络相关的事物,如HTTP请求等;
  5. UI后端(UI backend): 负责绘制提示框等浏览器组件,其底层使用的是操作系统的用户接口;
  6. JavaScript解释器(JavaScript interpreter): 负责解析和执行JavaScript代码;
  7. 数据存储(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双内核;

# 整个浏览器工作的流程

  1. 输入网址。
  2. 浏览器查找域名的IP地址。
  3. 浏览器给web服务器发送一个HTTP请求
  4. 网站服务的永久重定向响应
  5. 浏览器跟踪重定向地址 现在,浏览器知道了要访问的正确地址,所以它会发送另一个获取请求。
  6. 服务器“处理”请求,服务器接收到获取请求,然后处理并返回一个响应。
  7. 服务器发回一个HTML响应
  8. 浏览器开始显示HTML
  9. 浏览器发送请求,以获取嵌入在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 main flow

# 渲染步骤

以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 后,接下来介绍下浏览器渲染机制

Webkit main flow

浏览器解析渲染页面分为一下五个步骤:【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

小提示:如果你使用 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>
1
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
1
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 优化

  1. 避免 HTML 中书写 CSS 代码,因为这样难以维护。
  2. 使用 Viewport 加速页面的渲染。
  3. 使用语义化标签,减少 CSS 代码,增加可读性和 SEO。
  4. 减少标签的使用,DOM 解析是一个大量遍历的过程,减少不必要的标签,能降低遍历的次数。
  5. 避免 src、href 等的值为空,因为即时它们为空,浏览器也会发起 HTTP 请求。
  6. 减少 DNS 查询的次数
  7. 减少iframe负担;iframe会阻止页面的加载,而且即使iframe内容为空也会消耗时间,所以尽量避免使用;

# CSS 优化

  1. 优化选择器路径:使用 .c {} 而不是 .a .b .c {}
  2. 选择器合并:共同的属性内容提起出来,压缩空间和资源开销
  3. 精准样式:使用 padding-left: 10px 而不是 padding: 0 0 0 10px
  4. 雪碧图:将小的图标合并到一张图中,这样所有的图片只需要请求一次
  5. 避免通配符.a .b * {} 这样的选择器,根据从右到左的解析顺序在解析过程中遇到通配符 * {} 会遍历整个 DOM,性能大大损耗。
  6. 少用 floatfloat 在渲染时计算量比较大,可以使用 flex 布局
  7. 为 0 值去单位:增加兼容性。
  8. 压缩文件大小,减少资源下载负担。
  9. 用link代替@import:页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载; @import相当于将css放在网页内容底部。

# JavaScript 优化

  1. 尽可能把 <script> 标签放在 body 之后,避免 JS 的执行卡住 DOM 的渲染,最大程度保证页面尽快地展示出来。
  2. 尽可能合并 JS 代码:提取公共方法,进行面向对象设计等……
  3. CSS 能做的事情,尽量不用 JS 来做,毕竟 JS 的解析执行比较粗暴,而 CSS 效率更高
  4. 尽可能少地创建 DOM,而是在 HTML 和 CSS 中使用 display: none 来隐藏,按需显示 。其实应该用visibility:hidden;
  5. 压缩文件大小,减少资源下载负担。
  6. 尽可能逐条操作 DOM,并预定好 CSS 样式,从而减少 reflow 或者 repaint 的次数
  7. 减少重排与重绘:先让元素脱离文档流,处理完毕后再让元素回归文档流,这样浏览器只会进行两次重排与重绘(脱离时和回归时)
  8. 减少DOM元素数量:网页中元素过多对网页的加载和脚本的执行都是沉重的负担,500个元素和5000个元素在加载速度上会有很大差别。所以减少DOM元素数量是十分有必要的。
  9. 减少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 个思路了:

  • 减少像素点
  • 减少每个像素点能够显示的颜色
# 图片加载优化
  1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替
  2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中**(雪碧图);将多个图片合并到一个单独的图片中。如果用作背景图片,可以根据background-position来定位背景**,这样需要很多张背景图片现在就只需要一张了。也就是本来需要发送多次http请求来请求图片现在只需要发送一次就可以了。
  5. 选择正确的图片格式:
    • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
    • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
    • 照片使用 JPEG
  6. 内联图片 ;通过编码的字符串将图片内嵌到网页文本中。通过使用data: URL模式可以在web页面中包含图片但无需额外的http请求

# 网络加载方面优化

  • 服务端开启文件压缩功能

    • 启用GZIP压缩;保持良好的编程习惯,避免重复的CSS,JavaScript代码,多余的HTML标签和属性;
  • script 标签放在 body 底部,因为 JS 文件执行会阻塞渲染。

    • 当然也可以把 script 标签放在任意位置然后加上 defer ,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。
    • 对于没有任何依赖的 JS 文件可以加上 async ,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行
  • 执行 JS 代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑使用 WebworkerWebworker 可以让我们另开一个线程执行脚本而不影响渲染。

  • 使用多域名及使用内容发布网络(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 开发者工具分析页面性能的步骤如下。

  1. 打开 Chrome 开发者工具,切换到 Performance 面板
  2. 点击 Record 开始录制
  3. 刷新页面或展开某个节点
  4. 点击 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 请求数

  • 减少 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 结合 Aliasmod_rewrite 或者其他服务器类似功能实现跳转。
  • 缓存 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,避免重新下载。
  • 尽早输出缓冲
  • Ajax 请求使用 GET 方法
    • 浏览器执行 XMLHttpRequest POST 请求时分成两步,先发送 Header,再发送数据。而 GET 只使用一个 TCP 数据包发送数据,所以首选 GET 方法。
  • 避免图片 src 为空
    • src 请求的后果不容小觑: <img src="" /> var img = new Image(); img.src = "";
      • 给服务器造成意外的流量负担,尤其时日 PV 较大时;
      • 浪费服务器计算资源;
      • 可能产生报错。
  • 减少 Cookie 大小
    • Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。
      • 去除不必要的 Cookie;
      • 尽量压缩 Cookie 大小;
      • 注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;
      • 设置合适的过期时间。
  • 静态资源使用无 Cookie 域名
    • 静态资源一般无需使用 Cookie,可以把它们放在使用二级域名或者专门域名的无 Cookie 服务器上,降低 Cookie 传送的造成的流量浪费,提高响应速度。

# CSS

  • 把样式表放在 <head>
    • 如果把样式表放在页面底部,一些浏览器为减少重绘,会在 CSS 加载完成以后才渲染页面,用户只能对着白屏干瞪眼,用户体验极差。
  • 不要使用 CSS 表达式
    • CSS 表达式超出预期的频繁执行,页面滚动、鼠标移动时都会不断执行,带来很大的性能损耗。
  • 使用 <link> 替代 @import
    • 对于 IE 某些版本,@import 的行为和 `` 放在页面底部一样。所以,不要用它。
  • 不要使用 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 以后

    对于 resizescroll 等触发频率极高的事件,应该通过 debounce 等机制降低处理程序执行频率。

# 图片

  • 优化图片

    • PNG 终极优化;
    • Webp 相关内容;
    • SVG 相关内容。
  • 优化 CSS Sprite

    • 水平排列 Sprite 中的图片,垂直排列会增加图片大小
    • Spirite 中把颜色较近的组合在一起可以降低颜色数,理想状况是低于 256 色以适用 PNG8 格式;
    • 不要在 Spirite 的图像中间留有较大空隙。减少空隙虽然不太影响文件大小,但可以降低用户代理把图片解压为像素图的内存消耗,对移动设备更友好。
  • 不要在 HTML 中缩放图片

    • 不要使用 <img>widthheight 缩放图片,如果用到小图片,就使用相应大小的图片。

      很多 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

上次更新: 2022/04/15, 05:41:26
×