1 知识体系
1.1 从URL输入到页面加载
首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出我们的 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户
将这个过程切分为如下的过程片段
- DNS 解析
- TCP 连接
- HTTP 请求抛出
- 服务端处理请求,HTTP 响应返回
- 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
1.2 性能优化思维导图
2 网路篇(http)
输入 URL 到显示页面这个过程中,涉及到网络层面的,有三个主要过程:
- DNS 解析
- TCP 连接
- HTTP 请求/响应
2.1 DNS解析
一般来说,在前端优化中与 DNS 有关的有两点:
- 是减少DNS的请求次数,
- 是进行DNS预获取——DNS Prefetch
DNS 作为互联网的基础协议,其解析的速度似乎很容易被网站优化人员忽视。现在大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析需要耗费20-120 毫秒,减少DNS解析时间和次数是个很好的优化方式。
DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能减少用户的等待时间,提升用户体验 。
默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取,那么就要使用显示 DNS Prefetch 了。
DNS预解析具体用法
//用meta信息来告知浏览器, 当前页面要做DNS预解析 <meta http-equiv="x-dns-prefetch-control" content="on"> //在页面header中使用link标签来强制对DNS预解析: <link rel="dns-prefetch" href="//www.zhix.net">
注意:dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数,因为有开发者指出 禁用DNS 预读取能节省每月100亿的DNS查询 。
//如果需要禁止隐式的 DNS Prefetch <meta http-equiv="x-dns-prefetch-control" content="off">
2.2 HTTP
对于 DNS 解析和 TCP 连接两个步骤,我们前端可以做的努力非常有限。相比之下,HTTP 连接这一层面的优化才是我们网络优化的核心
HTTP 优化有两个大的方向
- 减少请求次数
- 减少单次请求所花费的时间
2.2.1减少HTTP请求次数
- 图片:雪碧图,图标字体文件
-
雪碧图
多张小图片合并为一张图,利用CSS -background-position调整图片显示位置 -
图标字体文件
阿里图标
- 合并JS和CSS文件
- webpack
- gulp
- grunt
- 浏览器缓存
如果图片或者脚本,样式文件内容比较固定,不经常被修改,那么,尽可能利用缓存技术,减少HTTP请求次数或文件下载次数
- 强缓存
- 协商缓存
2.2.2减少单次请求所花费的时间
- 图片
图片在线批量压缩 - gzip
如果是vue项目,还有nginx,哪么vue,nginx,服务器都要开启gzip
3.网络篇(图片优化)
3.1不同业务场景下的图片方案选型
前置知识:二进制位数与色彩的关系 在计算机中,像素用二进制数来表示。不同的图片格式中像素与二进制位数之间的对应关系是不同的。一个像素对应的二进制位数越多,它可以表示的颜色种类就越多,成像效果也就越细腻,文件体积相应也会越大。
3.2 JPEG/JPG
关键字:有损压缩、体积小、加载快、不支持透明
-
JPG 的优点
JPG 最大的特点是有损压缩。这种高效的压缩算法使它成为了一种非常轻巧的图片格式。另一方面,即使被称为“有损”压缩,JPG的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉——前提是你用对了业务场景。
-
使用场景
JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。
两大电商网站对大图的处理,是 JPG 图片应用场景的最佳写照: 打开淘宝首页,我们可以发现页面中最醒目、最庞大的图片,一定是以 .jpg 为后缀的: 使用 JPG 呈现大图,既可以保住图片的质量,又不会带来令人头疼的图片体积,是当下比较推崇的一种方案。
-
JPG 的缺陷
有损压缩在上文所展示的轮播图上确实很难露出马脚,但当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。此外,JPEG 图像不支持透明度处理,透明图片需要召唤 PNG 来呈现。
3.3 png
关键字:无损压缩、质量高、体积大、支持透明
-
PNG 的优点
PNG(可移植网络图形格式)是一种无损压缩的高保真的图片格式。8 和 24,这里都是二进制数的位数。按照我们前置知识里提到的对应关系,8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。
PNG 图片具有比 JPG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。它弥补了上文我们提到的 JPG 的局限性,唯一的 BUG 就是体积太大。
-
PNG-8 与 PNG-24 的选择题
什么时候用 PNG-8,什么时候用 PNG-24,这是一个问题
理论上来说,当你追求最佳的显示效果、并且不在意文件体积大小时,是推荐使用 PNG-24 的。
但实践当中,为了规避体积的问题,我们一般不用PNG去处理较复杂的图像。当我们遇到适合 PNG 的场景时,也会优先选择更为小巧的 PNG-8。
如何确定一张图片是该用 PNG-8 还是 PNG-24去呈现呢?好的做法是把图片先按照这两种格式分别输出,看 PNG-8输出的结果是否会带来肉眼可见的质量损耗,并且确认这种损耗是否在我们(尤其是你的 UI 设计师)可接受的范围内,基于对比的结果去做判断。
-
应用场景
前面我们提到,复杂的、色彩层次丰富的图片,用 PNG 来处理的话,成本会比较高,我们一般会交给 JPG 去存储。
考虑到 PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。
此时我们再次把目光转向性能方面堪称业界楷模的淘宝首页,我们会发现它页面上的 Logo,无论大小,还真的都是 PNG 格式:
3.4 SVG
关键字:文本文件、体积小、不失真、兼容性好
-
SVG 的使用方式与应用场景
将 SVG 写入 HTML
将 SVG 写入独立文件后引入 HTML将 SVG 写入 HTML
3.5 Base64
关键字:文本文件、依赖编码、小图标解决方案
-
Base64 的应用场景
图片的实际尺寸很小(大家可以观察一下掘金页面的 Base64 图,几乎没有超过 2kb 的)
图片无法以雪碧图的形式与其它小图结合(合成雪碧图仍是主要的减少 HTTP 请求的途径,Base64 是雪碧图的补充)
图片的更新频率非常低(不需我们重复编码和修改文件内容,维护成本较低)
3.6 WebP
关键字:年轻的全能型选手 是 Google 专为 Web 开发的一种旨在加快图片加载速度的图片格式,它支持有损压缩和无损压缩。
-
WebP 的优点
WebP 像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片——它集多种图片文件格式的优点于一身。
-
WebP 的局限性
兼容性
3.7 总结
不同业务场景下的图片方案选型
4.存储篇(浏览器缓存)
4.1 什么是缓存
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤
浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
缓存思维导图
4.2 缓存位置
4.2.1 缓存优先级
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
Service Worker
Memory Cache
Disk Cache
Push Cache
4.2.2 Service Worker
不了解 MDN
4.2.3 MemoryCache
MemoryCache,是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。
内存缓存是快的,也是“短命”的。它和渲染进程“生死相依”,当进程结束后,也就是 tab 关闭以后,内存里的数据也将不复存在。
那么哪些文件会被放入内存呢?
事实上,这个划分规则,一直以来是没有定论的。不过想想也可以理解,内存是有限的,很多时候需要先考虑即时呈现的内存余量,再根据具体的情况决定分配给内存和磁盘的资源量的比重——资源存放的位置具有一定的随机性
虽然划分规则没有定论,但根据日常开发中观察的结果,包括我们开篇给大家展示的 Network 截图,我们至少可以总结出这样的规律:资源存不存内存,浏览器秉承的是“节约原则”。我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。
4.2.4 Disk Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍
浏览器会把哪些文件丢进内存中?哪些丢进硬盘中 对于大文件来说,大概率是不存储在内存中的,反之优先 当前系统内存使用率高的话,文件优先存储进硬盘
4.2.5 Push Cache
不了解
push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用
4.3 缓存过程分析
上图我们可以知道
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
4.4 http缓存
HTTP 缓存是我们日常开发中最为熟悉的一种缓存机制。它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。
4.5 强缓存
强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
4.5.1 Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。
- 缓存过期时间,用来指定资源的到期时间,是服务器端的具体的时间点
- 告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而不用再次请求
- max-age的优化级高于expires,当有max-age的时候,会无视expires
- 当在有效时间内,如果服务器端的文件已经发生改变,但是浏览器端无法感知
4.5.2 Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令
- max-age
- s-maxage
- private
- public
- no-cache
- no-store
4.5.3 强缓存思维导图
4.6 协商缓存
4.6.1 什么是协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存生效,返回304和Not Modified
协商缓存失效,返回200和请求结果
5. 存储篇(本地存储)
5.1 Cookie
- Cookie 的本职工作并非本地存储,而是“维持状态”。
- Cookie 不够大
- 同一个域名下的所有请求,都会携带 Cookie
5.2 Local Storage,Session Storage
5.2.1 概述
存储容量大: Web Storage 根据浏览器的不同,存储容量可以达到 5-10M 之间 仅位于浏览器端,不与服务端发生通信。
5.2.2 Local Storage 与 Session Storage 的区别
-
生命周期: Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。
-
作用域: Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。
5.2.3 应用场景
-
Local Storage
- 理论上 Cookie 无法胜任的、可以用简单的键值对来存取的数据存储任务,都可以交给 Local Storage 来做。
- 存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串
- 存储一些不经常更新的 CSS、JS 等静态资源
-
Session Storage
微博的 Session Storage 就主要是存储你本次会话的浏览足迹
5.3 IndexedDB
- IndexedDB 是没有存储上限的(一般来说不会小于 250M)
- IndexedDB 可以看做是 LocalStorage 的一个升级,当数据的复杂度和规模上升到了 LocalStorage 无法解决的程度,我们毫无疑问可以请出 IndexedDB 来帮忙。
6. CDN
6.1 什么是CDN
CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求。 CDN 提供快速服务,较少受高流量影响。
6.2 为什么要用 CDN
缓存、本地存储带来的性能提升,是不是只能在“获取到资源并把它们存起来”这件事情发生之后?也就是说,首次请求资源的时候,这些招数都是救不了我们的。要提升首次请求的响应能力,我们还需要借助 CDN 的能力
7.渲染篇(服务端渲染)
7.1 客户端渲染
客户端渲染模式下,服务端会把渲染需要的静态文件发送给客户端,客户端加载过来之后,自己在浏览器里跑一遍 JS,根据 JS 的运行结果,生成相应的 DOM
<!doctype html> <html> <head> <title>我是客户端渲染的页面</title> </head> <body> <div id='root'></div> <script src='index.js'></script> </body> </html>
根节点下到底是什么内容呢?你不知道,我不知道,只有浏览器把 index.js 跑过一遍后才知道,这就是典型的客户端渲染。
页面上呈现的内容,你在 html 源文件里里找不到——这正是它的特点。
7.2 服务端渲染
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。
使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到
7.3 服务端渲染解决了什么性能问题
事实上,很多网站是出于效益(seo)的考虑才启用服务端渲染,性能倒是在其次。
假设 A 网站页面中有一个关键字叫“前端性能优化”,这个关键字是 JS 代码跑过一遍后添加到 HTML 页面中的。那么客户端渲染模式下,我们在搜索引擎搜索这个关键字,是找不到 A 网站的——搜索引擎只会查找现成的内容,不会帮你跑 JS 代码。A 网站的运营方见此情形,感到很头大:搜索引擎搜不出来,用户找不到我们,谁还会用我的网站呢?为了把“现成的内容”拿给搜索引擎看,A 网站不得不启用服务端渲染。
但性能在其次,不代表性能不重要。服务端渲染解决了一个非常关键的性能问题——首屏加载速度过慢。在客户端渲染模式下,我们除了加载 HTML,还要等渲染所需的这部分 JS 加载完,之后还得把这部分 JS 在浏览器上再跑一遍。这一切都是发生在用户点击了我们的链接之后的事情,在这个过程结束之前,用户始终见不到我们网页的庐山真面目,也就是说用户一直在等!相比之下,服务端渲染模式下,服务器给到客户端的已经是一个直接可以拿来呈现给用户的网页,中间环节早在服务端就帮我们做掉了,用户岂不“美滋滋”?。
7.4 服务端渲染的应用场景
服务端渲染本质上是本该浏览器做的事情,分担给服务器去做。这样当资源抵达浏览器时,它呈现的速度就快了
但仔细想想,在这个网民遍地的时代,几乎有多少个用户就有多少台浏览器。用户拥有的浏览器总量多到数不清,那么一个公司的服务器又有多少台呢?我们把这么多台浏览器的渲染压力集中起来,分散给相比之下数量并不多的服务器,服务器肯定是承受不住的
除非网页对性能要求太高了,以至于所有的招式都用完了,性能表现还是不尽人意,这时候我们就可以考虑向老板多申请几台服务器,把服务端渲染搞起来了~
8.渲染篇(浏览器渲染)
8.1 浏览器内核
浏览器内核可以分成两部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎
渲染引擎又包括了 HTML 解释器、CSS 解释器、布局、网络、存储、图形、音视频、图片解码器等等零部件。
8.2 浏览器渲染过程解析
8.3 基于渲染流程的 CSS 优化建议
8.3.1 CSS 选择符是从右到左进行匹配的
浏览器必须遍历页面上每个 li 元素,并且每次都要去确认这个 li 元素的父元素 id 是不是 myList
8.3.2 具体优化
- 避免使用通配符,只对需要用到的元素进行选择
- 关注可以通过继承实现的属性,避免重复匹配重复定义。
- 少用标签选择器。如果可以,用类选择器替代
- 不要画蛇添足,id 和 class 选择器不应该被多余的标签选择器拖后腿
- 减少嵌套。后代选择器的开销是最高的,因此我们应该尽量将选择器的深度降到最低(最高不要超过三层),尽可能使用类来关联每一个标签元素
8.4 告别阻塞:CSS 与 JS 的加载顺序优化
HTML、CSS 和 JS,都具有阻塞渲染的特性。 HTML 阻塞,天经地义——没有 HTML,何来 DOM?没有 DOM,渲染和优化,都是空谈。
8.4.1 CSS 的阻塞
在刚刚的过程中,我们提到 DOM 和 CSSOM 合力才能构建渲染树。这一点会给性能造成严重影响:默认情况下,CSS 是阻塞的资源。浏览器在构建 CSSOM 的过程中,不会渲染任何已处理的内容。即便 DOM 已经解析完毕了,只要 CSSOM 不 OK,那么渲染这个事情就不 OK(这主要是为了避免没有 CSS 的 HTML 页面丑陋地“裸奔”在用户眼前)。
只有当我们开始解析 HTML 后、解析到 link 标签或者 style 标签时,CSS 才登场,CSSOM 的构建才开始。 很多时候,DOM 不得不等待 CSSOM。因此我们可以这样总结:
CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
尽早(将 CSS 放在 head 标签里)和尽快(启用 CDN 实现静态资源加载速度的优化)
- css文件是并行下载的
- css的下载会阻塞后面js的执行
- css的下载不会阻塞后面的html的解析,但是会阻塞dom渲染
- css的下载会阻塞后面的DOM的onload事件。
- css的下载不会阻塞后面js的下载,但是js下载完成后,被阻塞执行。
8.4.2 JS 的阻塞
JS 的作用在于修改,它帮助我们修改网页的方方面面:内容、样式以及它如何响应用户交互。这“方方面面”的修改,本质上都是对 DOM 和 CSSDOM 进行修改。因此 JS 的执行会阻止 CSSOM,在我们不作显式声明的情况下,它也会阻塞 DOM。
JS 引擎是独立于渲染引擎存在的。我们的 JS 代码在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎。JS 引擎对内联的 JS 代码会直接执行,对外部 JS 文件还要先获取到脚本、再进行执行。等 JS 引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续 CSSOM 和 DOM 的构建。 因此与其说是 JS 把 CSS 和 HTML 阻塞了,不如说是 JS 引擎抢走了渲染引擎的控制权。