关于性能优化是个大的面,这篇文章主要涉及到前端的几个点,如前端性能优化的流程、常见技术手段、工具等。
提及前端性能优化,大家应该都会想到雅虎军规,本文会结合雅虎军规融入自己的了解知识,进行的总结和梳理 。
详情,可以查阅我的博客:https://lishaoy.net。
雅虎军规
首先,我们先来看看“雅虎军规”的35条:
尽量减少 HTTP 请求个数——须权衡
使用CDN(内容分发网络)
为文件头指定 Expires 或 Cache-Control ,使内容具有缓存性。
避免空的 src 和 href
使用 gzip 压缩内容
把 CSS 放到顶部
把 JS 放到底部
避免使用 CSS 表达式
将 CSS 和 JS 放到外部文件中
减少 DNS 查找次数
精简 CSS 和 JS
避免跳转
剔除重复的 JS 和 CSS
配置 ETags
使 AJAX 可缓存
尽早刷新输出缓冲
使用 GET 来完成 AJAX 请求
延迟加载
预加载
减少 DOM 元素个数
根据域名划分页面内容
尽量减少 iframe 的个数
避免 404
减少 Cookie 的大小
使用无 cookie 的域
减少 DOM 访问
开发智能事件处理程序
用 代替 @import
避免使用滤镜
优化图像
优化 CSS Spirite
不要在 HTML 中缩放图像——须权衡
favicon.ico要小而且可缓存
保持单个内容小于25K
打包组件成复合文本
如对雅虎军规的具体细则内容不是很了解,可自行去各搜索引擎搜索雅虎军规了解详情。
压缩 合并
对于前端性能优化自然要关注首屏打开速度,而这个速度,很大因素是花费在网络请求上,那么怎么减少网络请求的时间呢?
减少网络请求次数
减小文件体积
使用 加速
所以压缩、合并就是一个解决方案,当然可以用 、 、 等构建工具压缩、合并。
JS、CSS 压缩、合并
例如:gulp js、css 压缩、合并代码如下 :
然后,再把压缩、合并的 JS、CSS 放入 ,看看效果如何:
以上是 lishaoy.net 清除缓存后的首页请求速度。
可见,请求时间是4.59 s,总请求个数51, 而 的请求个数是8, 的请求个数是3(其实就 all.css 一个,其它 2 个是 Google浏览器加载的), 而没使用压缩、合并时候,请求时间是10多秒,总请求个数有70多个, 的请求个数是20多个 ,对比请求时间性能提升1倍多。
如图,有缓存下的首页效果:
基本都是秒开 。
Tips:在压缩、合并后,单个文件控制在 25 ~ 30 KB左右,同一个域下,最好不要多于5个资源。
图片压缩、合并
例如: 图片压缩代码如下 :
图片的合并可以采用 ,方法就是把一些小图用 合成一张图,用 定位显示每张图片的位置。
然后,把压缩的图片放入 ,看看效果如何:
可见,请求时间是1.70 s,总请求个数50, 而 的请求个数是15(这里因为首页都是大图,就没有合并,只是压缩了),但是,效果很好 ,从4.59 s缩短到1.70 s, 性能又提升一倍。
再看看有缓存情况如何 :
请求时间是1.05 s,有缓存和无缓存基本差不多。
Tips:大的图片在不同终端,应该使用不同分辨率,而不应该使用缩放(百分比)
整个压缩、合并(js、css、img)再放入 ,请求时间从10多秒 ,到最后的1.70 s,性能提升5倍多,可见,这个操作必要性。
缓存
缓存会根据请求保存输出内容的副本,例如页面、图片、文件,当下一个请求来到的时候:如果是相同的 ,缓存直接使 用本地的副本响应访问请求,而不是向源服务器再次发送请求。因此,可以从以下2个方面提升性能。
减少相应延迟,提升响应时间
减少网络带宽消耗,节省流量
我们用两幅图来了解下浏览器的缓存机制。
1、浏览器第一次请求
2、浏览器再次请求
从以上两幅图中,可以清楚的了解浏览器缓存的过程:
首次访问一个 ,没有缓存,但是,服务器会响应一些 信息,如: 等,来记录下次请求是否缓存、如何缓存。
再次访问这个 时候,浏览器会根据首次访问返回的 信息,来决策是否缓存、如何缓存。
我们重点来分析下第二幅图,其实是分两条线路,如下 。
第一条线路:当浏览器再次访问某个 时,会先获取资源的 信息,判断是否命中强缓存(cache-control和expires),如命中,直接从缓存获取资源,包括响应的 信息(请求不会和服务器通信),也就是强缓存,如图:
第二条线路:如没有命中强缓存,浏览器会发送请求到服务器,请求会携带第一次请求返回的有关缓存的 信息(Last-Modified/If-Modified-Since和Etag/If-None-Match),由服务器根据请求中的相关 信息来比对结果是否协商缓存命中;若命中,则服务器返回新的响应 信息更新缓存中的对应 信息,但是并不返回资源内容,它会告知浏览器可以直接从缓存获取;否则返回最新的资源内容,也就是协商缓存。
现在,我们了解到浏览器缓存机制分为强缓存、协商缓存,再来看看他们的区别 :
强缓存
与强缓存相关的 字段有两个:
1、expires
expires:这是 时的规范,它的值为一个绝对时间的GMT格式的时间字符串,如 ,如果发送请求的时间在expires之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源。
2、cache-control
cache-control: ,这是 时出现的 信息,主要是利用该字段的 值来进行判断,它是一个相对值;资源第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则未命中,cache-control除了该字段外,还有下面几个比较常用的设置值:
no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在 ,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
public:可以被所有的用户缓存,包括终端用户和 等中间代理服务器。
private:只能被终端用户的浏览器缓存,不允许 等中继缓存服务器对其缓存。
Tips:如果 cache-control 与 expires 同时存在的话,cache-control 的优先级高于 expires。
协商缓存
协商缓存都是由浏览器和服务器协商,来确定是否缓存,协商主要通过下面两组 字段,这两组字段都是成对出现的,即第一次请求的响应头带上某个字段(Last-Modified或者Etag),则后续请求会带上对应的请求字段(If-Modified-Since或者If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的字段。
1、Last-Modified/If-Modified-Since
二者的值都是 格式的时间字符串,具体过程:
浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在 的 加上Last-Modified字段,这个 字段表示这个资源在服务器上的最后修改时间。
浏览器再次跟服务器请求这个资源时,在 的 上加上If-Modified-Since字段,这个 字段的值就是上一次请求时返回的Last-Modified的值。
服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回 ,但是不会返回资源内容;如果有变化,就正常返回资源内容。当服务器返回 的响应时, 中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回 时的 。
浏览器收到 的响应后,就会从缓存中加载资源。
如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified的 在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值。
2、Etag/If-None-Match
这两个值是由服务器生成的每个资源的唯一标识字符串,只要资源有变化就这个值就会改变;其判断过程与Last-Modified、If-Modified-Since类似,与Last-Modified不一样的是,当服务器返回 的响应时,由于ETag重新生成过, 中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
Tips:Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
Service Worker
1、什么是 Service Worker
Service Worker本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API。
Service worker可以解决目前离线应用的问题,同时也可以做更多的事。Service Worker可以使你的应用先访问本地缓存资源,所以在离线状态时,在没有通过网络接收到更多的数据前,仍可以提供基本的功能(一般称之为 Offline First)。这是原生APP 本来就支持的功能,这也是相比于 ,原生 更受青睐的主要原因。
再来看看service worker能做些什么:
后台消息传递
网络代理,转发请求,伪造响应
离线缓存
消息推送
...
本文主要以(lishaoy.net)资源缓存为例,阐述下 service worker如何工作。
2、生命周期
service worker初次安装的生命周期,如图 :
从上 图可知,service worker工作的流程:
安装:通过 来获取和注册。
激活:当 安装完成后,会接收到一个激活事件(activate event)。 主要用途是清理先前版本的 脚本中使用的资源。
监听:两种状态
终止以节省内存;
监听获取 和消息 事件。
销毁:是否销毁由浏览器决定,如果一个 长期不使用或者机器内存有限,则可能会销毁这个 。
Tips:激活成功之后,在 Chrome 浏览器里,可以访问 chrome://inspect/#service-workers和 chrome://serviceworker-internals/ 可以查看到当前运行的service worker ,如图 :
现在,我们来写个简单的例子 。
3、注册 service worker
要安装 ,你需要在你的页面上注册它。这个步骤告诉浏览器你的 脚本在哪里。
上面的代码检查 是否可用,如果可用, 被注册。如果这个 已经被注册过,浏览器会自动忽略上面的代码。
4、激活 service worker
在你的 注册之后,浏览器会尝试为你的页面或站点安装并激活它。
事件会在安装完成之后触发。 事件一般是被用来填充你的浏览器的离线缓存能力。你需要为 事件定义一个 ,并决定哪些文件你想要缓存.
在我们的 中,我们需要执行以下步骤:
开启一个缓存
缓存我们的文件
决定是否所有的资源是否要被缓存
上面的代码中,我们通过 打开我们指定的 文件名,然后我们调用 并传入我们的文件数组。这是通过一连串(caches.open 和 cache.addAll)完成的。 拿到一个 并使用它来获得安装耗费的时间以及是否安装成功。
5、监听 service worker
现在我们已经将你的站点资源缓存了,你需要告诉 让它用这些缓存内容来做点什么。有了 事件,这是很容易做到的。
每次任何被 控制的资源被请求到时,都会触发 事件,我们可以给 添加一个 的事件监听器,接着调用 上的 方法来劫持我们的HTTP响应,然后你用可以用自己的方法来更新他们。
允许我们对网络请求的资源和 里可获取的资源进行匹配,查看是否缓存中有相应的资源。这个匹配通过 和 进行,就像正常的HTTP请求一样。
那么,我们如何返回 呢,下面 就是一个例子 :
上面的代码里我们定义了 事件,在 里,我们传入了一个由 产生的 查找 中被 缓存命中的 。
如果我们有一个命中的 ,我们返回被缓存的值,否则我们返回一个实时从网络请求 的结果。
6、sw-toolbox
当然,我也可以使用第三方库,例如:lishaoy.net 使用了sw-toolbox。
sw-toolbox使用非常简单,下面 就是 lishaoy.net 的一个例子 :
以上是注册一个 。
就这样搞定了 (具体的用法可以去 https://googlechromelabs.github.io/sw-toolbox/api.html#main 查看)。
有的同学就问, 这么好用,这个缓存空间到底是多大?其实,在Chrome可以看到,如图:
可以看到,大概有30G,我的站点只用了183MB,完全够用了 。
最后,来两张图:
由于,文章篇幅过长,后续还会继续总结架构方面的优化,例如:
bigpipe分块输出
bigrender分块渲染
...
以及,渲染方面的优化,例如:
requestAnimationFrame
well-change
硬件加速 GPU
...
以及,性能测试工具,例如:
PageSpeed
audits
...
相关讲堂推荐
领取专属 10元无门槛券
私享最新 技术干货