Table of contents
前言
聊一聊 前端性能优化
性能优化想必大家都知道,谈及性能优化,最直接的两个问题自然就是什么地方需要优化,以及怎么去优化!
在谈及这两个核心的问题之前,我们先来聊一聊,为什么需要性能优化?
性能优化是每一个开发者所必须面对的问题,随着为我们学习的深入,代码量越来越多,逻辑越来越复杂,对于资源的占用也越来越高。在这个时候,可能你自己的设备可以正常的运行。但是,一个产品是要面向用户的。如果在用户的设备上无法流畅运行的话,那么这个产品就不会是一个成功的产品。在这种时候,性能优化在这个时候就非常有必要性。
什么地方需要性能优化?
聊及这个问题,那就不得不提及那个经典的问题
从URL输入到页面加载完成,都发生了什么?
这个问题非常重要,首先,只有我们知道URL加载过程发生了什么,我们就能对这个过程进行分析,解读,从而进行优化!
站在性能优化的角度,我们来解读一下这个问题。首先,客户端是通过IP和服务端建立连接的。所以说,第一步就是要通过DNS(域名解析系统)将URL转换为对应的IP地址。然后,我们通过这个IP地址,与其所对应的服务器建立起了TCP连接。建立起了连接之后,客户端就会向服务端发起一个http请求,服务端接收到我们的数据之后,就会把我们要请求的数据放在http请求的响应里。到了这个时候,浏览器就可以根据拿到的响应,将内容解析,渲染到浏览器上,展示给用户。
总结一下,上面这一大段话总体来说分为五个阶段
- DNS解析
- TCP连接
- HTTP请求
- 服务端处理HTTP请求,返回响应
- 浏览器根据拿到的响应数据,解析响应内容,将解析的结果展示给用户
So,如果要对浏览器进行优化,这为五个地方是绕不开的。根据这几个方向进行处理,就可以很好的进行性能优化。
对应优化
DNS解析
俗话说得好,有DNS的地方,就有缓存。
简单的回顾一下DNS
全称 Domain Name System ,即域名系统。万维网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
通过域名,最终得到该域名对应的IP地址的过程叫做域名解析(或主机名解析)。
www.baidu.com (域名) - DNS解析 -> 39.156.69.79 (IP地址)
DNS缓存
浏览器、操作系统、Local DNS、根域名服务器,它们都会对DNS结果做一定程度的缓存。
DNS查询过程如下:
- 首先搜索浏览器自身的DNS缓存,如果存在,则域名解析到此完成。
- 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的hosts文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
- 如果本地hosts文件不存在映射关系,则查找本地DNS服务器(ISP服务器,或者自己手动设置的DNS服务器),如果存在,域名到此解析完成。
- 如果本地DNS服务器还没找到的话,它就会向根服务器发出请求,进行递归查询。
浏览器在获取网站域名的实际IP地址后会对其IP进行缓存,减少网络请求的损耗。每种浏览器都有一个固定的DNS缓存时间,其中Chrome的过期时间是1分钟,在这个期限内不会重新请求DNS。Chrome浏览器看本身的DNS缓存时间比较方便,在地址栏输入:
chrome://net-internals/#dns
DNS prefetch
提前解析之后可能会用到的域名,使解析结果缓存到系统缓存(Chromium)中,缩短DNS解析时间,来提高网站的访问速度。
DNS Prefetch 主要分为两个步骤
- html 源码下载完成后,会解析页面的包含链接的标签,提前查询对应的域名。
- 对于访问过的页面,浏览器会记录一份域名列表,当再次打开时,会在 html 下载的同时去解析 DNS。
DNS预解析分为自动解析和主动解析
-
自动解析
浏览器使用超链接的href属性来查找要预解析的主机名。当遇到a标签,浏览器会自动将href中的域名解析为IP地址,这个解析过程是与用户浏览网页并行处理的。但是为了确保安全性,在HTTPS页面中不会自动解析。
-
手动解析
在页面添加如下标记:
<link rel="dns-prefetch" href="https://fonts.gstatic.com/" />
上面的link标签会让浏览器预取”https://fonts.gstatic.com/“的解析
对于HTTPS网页,如果你希望它开启自动解析功能时,添加如下标记:
<meta http-equiv="x-dns-prefetch-control" content="on" />
希望在HTTP页面关闭自动解析功能时,添加如下标记:
<meta http-equiv="x-dns-prefetch-control" content="off" />
TCP链接
长连接、预连接
预连接
preconnect 允许浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,这包括 DNS 解析,TLS 协商,TCP 握手,这消除了往返延迟并为用户节省了时间。可以这样使用:
<link href="https://cdn.domain.com" rel="preconnect" crossorigin />
一些其他的pre
PreRender
这是一个非常猛的预加载,因为它可以加载文档的所有资源
<link rel="prerender" href="http://example.com" />
Preload
preload 顾名思义就是一种预加载的方式,它通过向浏览器声明一个需要提交加载的资源,当资源真正被使用的时候立即执行,就无需等待网络的消耗。
这和浏览器的预加载不同,浏览器的预加载HTML里声明的资源。而perload实际上已经突破了这个限制,它还可以加载在CSS和JavaScript中的资源。
用法:
<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style" />
<!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement("link");
link.rel = "preload";
link.as = "style";
link.href = "/path/to/style.css";
document.head.appendChild(link);
</script>
当浏览器解析到这些代码就会去加载 href 中对应的资源但不执行,待到真正使用到的时候再执行。
Prefetch
prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源。它的用法跟 preload 是一样的:
<link rel="prefetch" href="/path/to/style.css" as="style" />
注意:
- 不要将 preload 和 prefetch 进行混用,它们分别适用于不同的场景,对于同一个资源同时使用 preload 和 prefetch 会造成二次的下载。
- preload 字体不带 crossorigin 也将会二次获取! 确保你对 preload 的字体添加 crossorigin 属性,否则他会被下载两次,这个请求使用匿名的跨域模式。这个建议也适用于字体文件在相同域名下,也适用于其他域名的获取(比如说默认的异步获取)。
能看得见的优化——图片优化
最简单的图片优化——压缩图片
图片压缩工具:https://tinypng.com/
2021年,前端图片资源占比不断增加
推荐看看 HTTP-Archive 这个网站,它可以定期抓取Web站点,并记录资源使用的情况。我们可以看到世界范围内Web站点资源变化的趋势。
截止到2021年5月14日,过去一年Web资源请求的结果是这样的
我们可以看到,过去的一年,Web请求资源的大小提升的十分迅速。
而具体到图片这一个选项
虽然增长的没有总体资源那么迅速,但是我们可以看到,它的占比是十分巨大的,仅仅是单个的图片资源,就占了全部资源将近一半的比例。
所以,如果我们在图片优化优化方面有了长足的进步,那么对于前端方面整体的性能优化会有相当大的提升。
前端的图片选择
JPEG/JPG
- 关键字:有损压缩、体积小、加载快、不支持透明
- 优点:JPG 最大的特点是有损压缩。这种高效的压缩算法使它成为了一种非常轻巧的图片格式。另一方面,即使被称为“有损”压缩,JPG的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉——前提是你用对了业务场景。
- 缺点:有损压缩在上文所展示的轮播图上确实很难露出马脚,但当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。此外,JPEG 图像不支持透明度处理,透明图片需要召唤 PNG 来呈现。
- 使用场景:JPG的特点决定了它适合使用在那种需要色彩丰富的图片的场景中。像一些背景图,banner大图,一般用的都是JPG图片.
广大的电商网站(例如淘宝),首页最醒目的,色彩最丰富的大图,往往使用的都是JPG
PNG-8 与 PNG-24
- 关键字:无损压缩、质量高、体积大、支持透明
- 优点:PNG是一种无损压缩的高保真的图片格式。8 和 24,这里都是二进制数的位数。8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。PNG 图片具有比 JPG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。
- 缺点:体积太大
- 使用场景:考虑到 PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。比如一些小的logo和图片
SVG
- 关键字:文本文件、体积小、不失真、兼容性好
- 优点::SVG 与 PNG 和 JPG 相比,文件体积更小,可压缩性更强。作为矢量图,它最显著的优势还是在于图片可无限放大而不失真这一点上。这使得 SVG 即使是被放到视网膜屏幕上,也可以一如既往地展现出较好的成像品质——1 张 SVG 足以适配 n 种分辨率。此外,SVG 是文本文件。我们既可以像写代码一样定义 SVG,把它写在 HTML 里、成为 DOM 的一部分,具有较强的灵活性。
- 缺点:渲染成本比较高,且存在着其它图片格式所没有的学习成本(它是可编程的)。
- 使用场景:在我们需要使用到对兼容性和缩放清晰度要求较高的图片时,SVG是一个非常好的选择
Base64
在讲Base64之前,我们先说一说另一种前端图片解决方案——雪碧图(CSS Sprites)
雪碧图、CSS 精灵、CSS Sprites、图像精灵,说的都是这个东西——一种将小图标和背景图像合并到一张图片上,然后利用 CSS 的背景定位来显示其中的每一部分的技术。
MDN上的解释:图像精灵(sprite,意为精灵),被运用于众多使用大量小图标的网页应用之上。它可取图像的一部分来使用,使得使用一个图像文件替代多个小文件成为可能。相较于一个小图标一个图像文件,单独一张图片所需的 HTTP 请求更少,对内存和带宽更加友好。
通过上面的介绍,我们知道了,雪碧图的核心其实就是通过把许多小图片放在一张大图上,减少请求图片的次数。Base64也是类似
Base64 并非一种图片格式,而是一种编码方式。Base64 和雪碧图一样,是作为小图标解决方案而存在的。
- 关键字:文本文件、依赖编码、小图标解决方案
- 优点:Base64 是一种用于传输 8 Bit 字节码的编码方式,通过对图片进行 Base64 编码,我们可以直接将编码结果写入 HTML 或者写入 CSS,从而减少 HTTP 请求的次数。
- 缺点:Base64 编码后,图片大小会膨胀为原文件的 4/3(这是由 Base64 的编码原理决定的)。
- 使用场景
- 图片的实际尺寸很小(8 kb 以下)
- 图片无法以雪碧图的形式与其它小图结合(合成雪碧图仍是主要的减少 HTTP 请求的途径,Base64 是雪碧图的补充)
- 图片的更新频率非常低(不需我们重复编码和修改文件内容,维护成本较低)
Base64的工具推荐——webpack 的 url-loader
WebP
WebP 是今天在座各类图片格式中最年轻的一位,它于 2010 年被提出, 是 Google 专为 Web 开发的一种旨在加快图片加载速度的图片格式,它支持有损压缩和无损压缩。
-
关键字:年轻的全能型选手
-
优点:WebP 像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片——它集多种图片文件格式的优点于一身。
官方介绍:与 PNG 相比,WebP 无损图像的尺寸缩小了 26%。在等效的 SSIM 质量指数下,WebP 有损图像比同类 JPEG 图像小 25-34%。
-
缺点:WebP 纵有千般好,但它毕竟太年轻。我们知道,任何新生事物,都逃不开兼容性的大坑。现在是 2021 年 5 月,WebP 的支持情况是这样的:
此外,WebP 还会增加服务器的负担——和编码 JPG 文件相比,编码同样质量的 WebP 文件会占用更多的计算资源。
-
使用场景:现在限制我们使用 WebP 的最大问题不是“这个图片是否适合用 WebP 呈现”的问题,而是“浏览器是否允许 WebP”的问题。在现在来说,WebP这个格式是使用的非常少的。
浏览器相关
之前浏览器的一些基础知识里已经说过了。这里主要是一些复习和补充
复习一些和浏览器的知识
进程和线程
-
进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
-
线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
浏览器是多进程的
浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。
在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了 (所以每一个Tab标签对应一个进程并不一定是绝对的)
浏览器的渲染过程
这张图对于浏览器的渲染过程有一个较为清晰的描述,其中的了解渲染树对浏览器渲染的理解会有一个更为清晰的认识!
- DOM 树:解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为“内容树”。
- CSSOM 树:解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的。
- 渲染树:CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。
- 布局渲染树:从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。
- 绘制渲染树: 遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。
浏览器缓存机制
缓存从位置上来说,分为四种,当我们查找缓存数据找不到的时候,我们才会去请求网络
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
Service Worker
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
Memory Cache
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
但我们要知道,内存是非常宝贵的资源,其中可以提供给浏览器使用的缓存是非常小的,所以我们在使用这一部分的缓存的时候一定要精打细算。
我们之前提到的一个pre prefetch 就是存在这个缓存里的。
Dish Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。
Push Cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
Push Cache 的一些特性:
-
所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
-
可以推送 no-cache 和 no-store 的资源
-
一旦连接被关闭,Push Cache 就被释放
-
多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
-
Push Cache 中的缓存只能被使用一次
-
浏览器可以拒绝接受已经存在的资源推送
-
你可以给其他域名推送资源
缓存的选择
- 对于大文件来说,大概率是不存储在内存中的,反之优先
- 当前系统内存使用率高的话,文件优先存储进硬盘
大多数缓存文件都是存储在Disk Cache中的
用户行为对浏览器缓存的影响
-
打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
-
普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
-
强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有
Cache-control: no-cache
(为了兼容,还带了Pragma: no-cache
),服务器直接返回 200 和最新内容。
本地储存
Cookie
Cookie 的本职工作并非本地存储,而是“维持状态”。
在 Web 开发的早期,人们亟需解决的一个问题就是状态管理的问题:HTTP 协议是一个无状态协议,服务器接收客户端的请求,返回一个响应,故事到此就结束了,服务器并没有记录下关于客户端的任何信息。那么下次请求的时候,如何让服务器知道“我是我”呢?
在这样的背景下,Cookie 应运而生。
Cookie 说白了就是一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”。它可以携带用户信息,当服务器检查 Cookie 的时候,便可以获取到客户端的状态。
劣势
-
不够大
大家知道,Cookie 是有体积上限的,它最大只能有 4 KB。当 Cookie 超过 4 KB 时,它将面临被裁切的命运。这样看来,Cookie 只能用来存取少量的信息。
-
过量的Cookie会造成大量的性能浪费
随着前端应用复杂度的提高,Cookie 也渐渐演化为了一个“存储多面手”——它不仅仅被用于维持状态,还被塞入了一些乱七八糟的其它信息,被迫承担起了本地存储的“重任”。在没有更好的本地存储解决方案的年代里,Cookie 小小的身体里承载了 4 KB 内存所不能承受的压力。
为了弥补 Cookie 的局限性,让“专业的人做专业的事情”,Web Storage 出现了。
Web Storage
Web Storage 是 HTML 5 专门为浏览器存储而提供的数据存储机制。它又分为 Local Storage 与 Session Storage。这两组概念非常相近,我们不妨先理解它们之间的区别,再对它们的共性进行研究。
Local Storage 与 Session Storage 的区别
两者的区别在于生命周期与作用域的不同。
- 生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。
- 作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。
Web Storage 的特性
- 存储容量大:Web Storage 根据浏览器的不同,存储容量可以达到 5-10 M 之间。
- 仅位于浏览器端,不与服务端发生通信。
IndexedDB
IndexedDB 是一个运行在浏览器上的非关系型数据库。既然是数据库了,那就不是 5 M、10 M 这样小打小闹级别了。理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250 M)。它不仅可以存储字符串,还可以存储二进制数据。
IndexedDB 可以看做是 Local Storage 的一个升级,当数据的复杂度和规模上升到了 Local Storage 无法解决的程度,我们毫无疑问可以请出 IndexedDB 来帮忙。
在一些需要写博客的网站,比如CSDN、慕课网 都看到了有使用 IndexedDB,用 IndexedDB 来存储文章草稿似乎很合适。
HTTP相关
针对网络优化,我们应该做的
-
减少DNS查询
每一次域名解析都需要一次网络往返,增加请求的延时,在查询期间会阻塞网络请求
-
减少HTTP请求
任何请求都不如不请求快,因此,去掉那些没有必要的请求
-
使用CDN
从物理上把数据放到离客户端近的地方,可以显著减少每次TCP连接的网路时延,增加吞吐量
-
Gzip资源
所有文本资源都应该使用Gzip进行压缩,然后再在服务端和客户端之间进行传输。一般来说,Gzip可以减少60%-80%文件的大小,也是一个相对简单,但是能取得明显效果的一个选项
-
避免HTTP重定向
HTTP重定向十分耗时,特别是把一个客户端的重定向到一个完全不同的域名的情况下,还会导致产生额外的DNS查询,TCP连接延时等。
HTTP管道
在HTTP 1.1中,新增了两个十分有用的特性
-
持久连接:TCP默认不关闭,可以被多个请求复用,不用声明
Connection: keep-alive
。客户端和服务端在发现对方有一段时间没有活动了,就可以主动的关闭连接。规范一些的做法是,客户端在发送最后一个请求是,发送Connention: close
,明确要求关闭TCP连接。 -
管道机制:在同一个TCP连接里,客户端可以同时发送多个请求。这个特性进一步的提升了HTTP协议的效率。
如:客户端要发送多个请求,之前的做法是,在同一个TCP连接里,先发送第一个请求,然后等待第一个请求的回应,收到了之后再发送下一个请求。管道机制则允许浏览器同时发送多个请求,但服务器还是按照顺序,先回应第一个发送的请求,再回应之后发送的请求。
HTTP 1.1 管道的好处就是消除了发送请求和响应的等待时间。这种并行处理请求的能力对于应用性能的提升相当的大。
我们向服务器发送多个请求,服务器会先处理第一个请求,然后返回响应。接着,处理第二个请求,然后返回第二个响应。这样会产生相当多的往返延时。
持久的TCP连接获取数据:通过尽早的派发请求,不被每次响应阻塞,可以消除额外的网络往返。这样就从非持久连接下每次请求两次往返,变成了整个请求队列只需要两次往返。
进一步的,如果我们在服务器上并行处理请求的话,还能进一步的减少请求的时间。
但是,HTTP 1.x 的返回格式具有一定的局限性。我们虽然可以并行的发送请求。但是服务端并不允许相应信息交错到达。响应顺序是严格串行返回的,先发送的请求必定会先返回响应。即使后发送的请求先处理完成。
上图:
- HTML和CSS请求同时到达,但是服务器先处理的是HTML请求
- 服务器并行处理,先处理HTML请求,后处理CSS请求。
- CSS请求先处理完成,但是并没有立即被服务器返回,而是被缓冲器来等待HTML完成
- 发送完HTML响应后,再发送服务器缓冲中的CSS响应
所以,虽然HTTP管道对性能的提升很明显,但因为队首阻塞的现象存在,产生了一系列的副作用
- 一个慢响应会阻塞所有的后续请求
- 并行处理请求时,服务器必须缓冲管道中的响应,从而占用服务器的资源,如果有一个响应非常大的话,很容易形成服务器的受攻击面
- 响应失败很容易造成TCP连接的终止,从而强制用户重复发送后续所有资源的请求,导致重复的处理
- 由于可能存在中间代理,所以检测管道的兼容性,可靠性十分重要
- 如果中间代理不支持管道,那么它可能会中断连接,也可能会把所有的请求串联起来
由于存在着这一系列的副作用,而HTTP 1.1 的标准中却并未对此做出说明。所以,尽管HTTP管道技术的优点毋庸置疑,但是应用仍然十分有限。一些支持管道功能的浏览器,通常都将其作为一个高级选项,但大多是浏览器通常会禁用它。
连接和拼合
最快的请求就是不用请求。不管使用什么协议,还是再什么类型的应用中,减少请求次数总是最好的性能优化手段。如果实在是无法减少网络请求的话,那么,对于HTTP 1.x 来言,可以考虑把多个资源打包捆绑到一块,然后通过一个网络请求来获取。
-
连接
把多个JavaScript或CSS组合成一个文件
-
拼合
把多张图片组合成一个更大的符合图片
对于JavaScript和CSS来说,只要保持一定的顺序,就可以做到在保证代码的性能和执行的基础上把多个文件连接起来。对于图片来说,本文之前提到过的雪碧图就是这个思想的一个具体实现。
连接和拼合技术都是属于以内容为中心的应用层优化,它们通过减少网络的往返开销,可以获得明显的性能提升。然而,实现这些技术也需要额外的处理,部署和编码,因而会给应用带来额外的复杂性。此外,把多个资源打包到一起,也可能对缓存带来额外的负担,影响页面的执行速度。
对于副作用的理解,我们举一个简单的例子:一个包含了十来个JavaScript和CSS文件的应用,在产品状态下把所有的文件合并为一个JavaScript和一个CSS文件。
- 相同类型的资源都位于一个URL(缓存)下面
- 资源包可能包含当前页面不需要的内容
- 对资源包中任何文件的更新都需要重新下载整个资源包,造成较高的资源开销
- JavaScript和CSS只有在传输完毕之后才会被解析执行,因此会拖慢应用的执行速度。
在实际中,大多数的Web应用不只有一个页面,而是由多个视图构成的。每个视图都有自己的资源,同时资源之间还有部分重叠(公用的CSS,JavaScript和图片)。其实,我们可以把它看成是一种预获取,代价是降低了初始的启动速度。
内存占用也是一个问题。对于雪碧图来说,浏览器必须分析整个图片,即使实际上只是显示了图片中的一小块,也要把整个图片保存在内存中。浏览器是不会把不显示的部分剔除掉的。
对于打包到多少大小合适,实际上并没有一个定论。然而,谷歌的测试团队PageSpeed表明,30~50 k(压缩后),是每个JavaScript文件比较合适的大小范围。即达到了能够减少小文件带来的网络延迟,还能确保文件的执行速度不会有太大的影响。当然,具体的结果可能会因为应用类型和脚本数量的影响有所不同。
我们所需要做的权衡
- 应用在下载很多小型资源的时候是否会被阻塞?
- 有选择地组合一些请求对你的应用是否有好处?
- 放弃缓存粒度对用户是否有影响?
- 组合图片是否会占用过多的内存?
- 首次渲染是否会造成延迟执行?
如何平衡两者之间的关系,从而达到一个最佳性能的平衡点,是一个艺术!
懒加载
初识
懒加载也叫延迟加载,指的是在长网页中延迟加载图像,是一种很好优化网页性能的方式。用户滚动到它们之前,可视区域外的图像不会加载。这与图像预加载相反,在长网页上使用延迟加载将使网页加载更快。在某些情况下,它还可以帮助减少服务器负载。常适用图片很多,页面很长的电商网站场景中。
为什么要使用懒加载
- 提升用户体验
- 减少无效资源的加载
- 防止并发加载资源过多会阻塞JavaScript的加载
实现原理
首先将页面上的图片的 src
属性设为空字符串,而图片的真实路径则设置在data-src
属性中, 当页面滚动的时候需要去监听scroll
事件,在scroll
事件的回调中,判断我们的懒加载的图片是否进入可视区域,如果图片在可视区内将图片的src
属性设置为data-src
的值,这样就可以实现延迟加载。
// 获取所有的图片标签
const imgs = document.getElementsByTagName("img");
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
let num = 0;
function lazyload() {
for (let i = num; i < imgs.length; i++) {
// 用可视区域高度减去元素顶部距离可视区域顶部的高度
let distance = viewHeight - imgs[i].getBoundingClientRect().top;
// 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
if (distance >= 0) {
// 给元素写入真实的src,展示图片
imgs[i].src = imgs[i].getAttribute("data-src");
// 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
num = i + 1;
}
}
}
// 监听Scroll事件
window.addEventListener("scroll", lazyload, false);
CDN
初识
主要思路: 尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定
实现方法: 通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接和负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上,加快访问速度
目的: 使用户可就近取得所需内容,解决Internet网络拥挤的状况,提高用户访问网站的响应速度
优势:
- CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。
工作原理
-
用户向浏览器输入
www.taobao.com
这个域名,浏览器第一次发现本地没有DNS缓存,则向网站的DNS服务器请求 -
网站的DNS域名解析器设置了CNAME,指向了
g.alicdn.com
,请求指向了CDN网络中的智能DNS负载均衡系统 -
智能DNS负载均衡系统解析域名,把对用户响应速度最快的IP节点(CDN服务器)返回给用户
-
用户向该IP节点(CDN服务器)发出请求
-
由于是第一次访问,CDN服务器会向原web站点请求,并缓存内容
-
请求结果发给用户
解决了哪些问题
- 缓解甚至消除了不同运营商之间互联的瓶颈造成的影响
- 减轻了各省的出口带宽压力
- 缓解了骨干网的压力
- 优化了网上热点内容的分布