关于 性能优化 是个大的面,这篇文章主要涉及到 前端 的几个点,如 前端性能优化 的流程、常见技术手段、工具等。javascript
说起 前端性能优化 ,你们应该都会想到 雅虎军规,本文会结合 雅虎军规 融入本身的了解知识,进行的总结和梳理 😜css
详情,能够查阅个人 博客 lishaoy.nethtml
首先,咱们先来看看 👀 雅虎军规 的 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 访问
- 开发智能事件处理程序
- 用 <link> 代替 @import
- 避免使用滤镜
- 优化图像
- 优化 CSS Spirite
- 不要在 HTML 中缩放图像——须权衡
- favicon.ico要小并且可缓存
- 保持单个内容小于25K
- 打包组件成复合文本
如对 雅虎军规 的具体细则内容不是很了解,可自行去各搜索 🔍 引擎 ,搜索 雅虎军规 了解详情。java
对于 前端性能优化 天然要关注 首屏 打开速度,而这个速度,很大因素是花费在网络请求上,那么怎么减小网络请求的时间呢?jquery
CDN
加速因此 压缩、合并 就是一个解决方案,固然能够用 gulp
、 webpack
、 grunt
等构建工具 压缩、合并webpack
JS、CSS
压缩 合并例如:gulp js、css
压缩、合并代码以下 👇git
//压缩、合并js gulp.task('scripts', function () { return gulp.src([ './public/lib/fastclick/lib/fastclick.min.js', './public/lib/jquery_lazyload/jquery.lazyload.js', './public/lib/velocity/velocity.min.js', './public/lib/velocity/velocity.ui.min.js', './public/lib/fancybox/source/jquery.fancybox.pack.js', './public/js/src/utils.js', './public/js/src/motion.js', './public/js/src/scrollspy.js', './public/js/src/post-details.js', './public/js/src/bootstrap.js', './public/js/src/push.js', './public/live2dw/js/perTips.js', './public/live2dw/lib/L2Dwidget.min.js', './public/js/src/love.js', './public/js/src/busuanzi.pure.mini.js', './public/js/src/activate-power-mode.js' ]).pipe(concat('all.js')).pipe(minify()).pipe(gulp.dest('./public/dist/')); }); // 压缩、合并 CSS gulp.task('css', function () { return gulp.src([ './public/lib/font-awesome/css/font-awesome.min.css', './public/lib/fancybox/source/jquery.fancybox.css', './public/css/main.css', './public/css/lib.css', './public/live2dw/css/perTips.css' ]).pipe(concat('all.css')).pipe(minify()).pipe(gulp.dest('./public/dist/')); });
而后,再把 压缩、合并 的 JS、CSS
放入 CDN
, 👀 看看效果如何github
如图: 压缩、合并 且放入 CND
以后的效果 web
以上是 lishaoy.net 清除缓存后的 首页 请求速度。
可见,请求时间是 4.59 s ,总请求个数 51 , 而 js
的请求个数是 8 ,css
的请求个数是 3 _(其实就 all.css 一个,其它 2 个是 Google浏览器加载的)_, 而没使用 压缩、合并 时候,请求时间是 10 多秒,总请求个数有 70 多个,js
的请求个数是 20 多个 ,对比请求时间 性能 提高 1倍 多
如图:有缓存下的首页效果
基本都是秒开 😝
Tips:在 压缩、合并 后,单个文件控制在 25 ~ 30 KB左右,同一个域下,最好不要多于5个资源
例如:gulp
图片压缩代码以下 👇
//压缩image gulp.task('imagemin', function () { gulp.src('./public/**/*.{png,jpg,gif,ico,jpeg}') .pipe(imagemin()) .pipe(gulp.dest('./public')); });
图片的合并能够采用 CSS Spirite
,方法就是把一些小图用 PS
合成一张图,用 css
定位显示每张图片的位置
.top_right .phone { background: url(../images/top_right.png) no-repeat 7px -17px; padding: 0 38px; } .top_right .help { background: url(../images/top_right.png) no-repeat 0 -47px; padding: 0 38px; }
而后,把 压缩 的图片放入 CDN
, 👀 看看,效果如何
可见,请求时间是 1.70 s ,总请求个数 50 , 而 img
的请求个数是 15 (这里由于首页都是大图,就没有合并,只是压缩了) ,可是,效果很好 😀 ,从 4.59 s 缩短到 1.70 s, 性能又提高一倍。
再看看有缓存状况如何 😏
请求时间是 1.05 s ,有缓存和无缓存基本差很少
Tips:大的图片在不一样终端,应该使用不一样分辨率,而不该该使用缩放(百分比)
整个 压缩、合并 (js、css、img) 再放入 CDN
,请求时间从 10 多秒 ,到最后的 1.70 s ,性能提高 5 倍多,可见,这个操做必要性。
缓存会根据请求保存输出内容的副本,例如 页面、图片、文件,当下一个请求来到的时候:若是是相同的URL
,缓存直接使 用本地的副本响应访问请求,而不是向源服务器再次发送请求。所以,能够从如下 2 个方面提高性能。
咱们用两幅图来了解下浏览器的 缓存机制
浏览器第一次请求
浏览器再次请求
从以上两幅图中,能够清楚的了解浏览器 缓存 的过程。
首次访问一个 URL
,没有 缓存 ,可是,服务器会响应一些 header
信息,如:expires、cache-control、last-modified、etag
等,来记录下次请求是否缓存、如何缓存。
再次访问这个 URL
时候,浏览器会根据首次访问返回的 header
信息,来决策是否缓存、如何缓存。
咱们重点来分析下第二幅图,实际上是分两条线路,以下 👇
URL
时,会先获取资源的 header
信息,判断是否命中强缓存 (cache-control和expires) ,如命中,直接从缓存获取资源,包括响应的 header
信息 (请求不会和服务器通讯) ,也就是 强缓存 ,如图header
信息 (Last-Modified/If-Modified-Since和Etag/If-None-Match) ,由服务器根据请求中的相关 header
信息来比对结果是否协商缓存命中;若命中,则服务器返回新的响应 header
信息更新缓存中的对应 header
信息,可是并不返回资源内容,它会告知浏览器能够直接从缓存获取;不然返回最新的资源内容,也就是 协商缓存。如今,咱们了解到浏览器缓存机制分为 强缓存、协商缓存,再来看看他们的区别 👇
缓存策略 | 获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|---|
强缓存 | 从缓存取 | 200(from memory cache) | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(not modified) | 是,经过服务器来告知缓存是否可用 |
与强缓存相关的 header
字段有两个:
expires: 这是 http1.0
时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,如 Mon, 10 Jun 2015 21:31:12 GMT
,若是发送请求的时间在 expires 以前,那么本地缓存始终有效,不然就会发送请求到服务器来获取资源
cache-control: max-age=number
,这是 http1.1
时出现的 header
信息,主要是利用该字段的 max-age
值来进行判断,它是一个相对值;资源第一次的请求时间和 Cache-Control 设定的有效期,计算出一个资源过时时间,再拿这个过时时间跟当前的请求时间比较,若是请求时间在过时时间以前,就能命中缓存,不然未命中, cache-control 除了该字段外,还有下面几个比较经常使用的设置值:
ETag
,那么请求的时候会与服务端验证,若是资源未被更改,则能够避免从新下载。CDN
等中间代理服务器。CDN
等中继缓存服务器对其缓存。Tips:若是 cache-control 与 expires 同时存在的话,cache-control 的优先级高于 expires
协商缓存都是由浏览器和服务器协商,来肯定是否缓存,协商主要经过下面两组 header
字段,这两组字段都是成对出现的,即第一次请求的响应头带上某个字段 ( Last-Modified 或者 Etag ) ,则后续请求会带上对应的请求字段 (If-Modified-Since 或者 If-None-Match ) ,若响应头没有 Last-Modified 或者 Etag 字段,则请求头也不会有对应的字段。
两者的值都是 GMT
格式的时间字符串,具体过程:
respone
的 header
加上 Last-Modified 字段,这个 header
字段表示这个资源在服务器上的最后修改时间request
的 header
上加上 If-Modified-Since 字段,这个 header
字段的值就是上一次请求时返回的 Last-Modified 的值304 Not Modified
,可是不会返回资源内容;若是有变化,就正常返回资源内容。当服务器返回 304 Not Modified
的响应时,response header
中不会再添加 Last-Modified的header ,由于既然资源没有变化,那么 Last-Modified 也就不会改变,这是服务器返回 304
时的 response header
304
的响应后,就会从缓存中加载资源Header
在从新加载的时候会被更新,下次请求时,If-Modified-Since 会启用上次返回的Last-Modified 值这两个值是由服务器生成的每一个资源的惟一标识字符串,只要资源有变化就这个值就会改变;其判断过程与 Last-Modified、If-Modified-Since 相似,与 Last-Modified 不同的是,当服务器返回 304 Not Modified
的响应时,因为 ETag 从新生成过,response header
中还会把这个 ETag 返回,即便这个 ETag 跟以前的没有变化。
Tips:Last-Modified与ETag是能够一块儿使用的,服务器会优先验证ETag,一致的状况下,才会继续比对Last-Modified,最后才决定是否返回304。
Service Worker 本质上充当Web应用程序与浏览器之间的代理服务器,也能够在网络可用时做为浏览器和网络间的代理。它们旨在(除其余以外)使得可以建立有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采起适当的动做。他们还容许访问推送通知和后台同步API。
Service worker 能够解决目前离线应用的问题,同时也能够作更多的事。 Service Worker 可使你的应用先访问本地缓存资源,因此在离线状态时,在没有经过网络接收到更多的数据前,仍能够提供基本的功能(通常称之为 Offline First)。这是原生APP 原本就支持的功能,这也是相比于 web app
,原生 app
更受青睐的主要缘由。
再来看看 👀 service worker 能作些什么:
本文主要以(lishaoy.net)资源缓存为例,阐述下 service worker如何工做
service worker 初次安装的生命周期,如图 🌠
从上 👆 图可知,service worker 工做的流程:
service worker URL
经过 serviceWorkerContainer.register()
来获取和注册。service worker
安装完成后,会接收到一个激活事件(activate event)。 onactivate
主要用途是清理先前版本的 service worker
脚本中使用的资源。监听: 两种状态
fetch
和消息 message
事件。service worker
长期不使用或者机器内存有限,则可能会销毁这个 worker
。Tips:激活成功以后,在 Chrome 浏览器里,能够访问 chrome://inspect/#service-workers和 chrome://serviceworker-internals/ 能够查看到当前运行的service worker ,如图 👇。
如今,咱们来写个简单的例子 🌰
要安装 service worker
,你须要在你的页面上注册它。这个步骤告诉浏览器你的 service worker
脚本在哪里。
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); }); }
上面的代码检查 service worker API
是否可用,若是可用,service worker /sw.js
被注册。若是这个 service worker
已经被注册过,浏览器会自动忽略上面的代码。
在你的 service worker
注册以后,浏览器会尝试为你的页面或站点安装并激活它。 install
事件会在安装完成以后触发。install
事件通常是被用来填充你的浏览器的离线缓存能力。你须要为 install
事件定义一个 callback
,并决定哪些文件你想要缓存.
// The files we want to cache var CACHE_NAME = 'my-site-cache-v1'; var urlsToCache = [ '/', '/css/main.css', '/js/main.js' ]; self.addEventListener('install', function(event) { // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); });
在咱们的 install callback
中,咱们须要执行如下步骤:
上面的代码中,咱们经过 caches.open
打开咱们指定的 cache
文件名,而后咱们调用 cache.addAll
并传入咱们的文件数组。这是经过一连串 promise
(caches.open 和 cache.addAll) 完成的。event.waitUntil
拿到一个 promise
并使用它来得到安装耗费的时间以及是否安装成功。
如今咱们已经将你的站点资源缓存了,你须要告诉 service worker
让它用这些缓存内容来作点什么。有了 fetch
事件,这是很容易作到的。
每次任何被 service worker
控制的资源被请求到时,都会触发 fetch
事件,咱们能够给 service worker
添加一个 fetch
的事件监听器,接着调用 event
上的 respondWith()
方法来劫持咱们的 HTTP 响应,而后你用能够用本身的方法来更新他们。
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request); ); });
caches.match(event.request)
容许咱们对网络请求的资源和 cache
里可获取的资源进行匹配,查看是否缓存中有相应的资源。这个匹配经过 url
和 vary header
进行,就像正常的 HTTP 请求同样。
那么,咱们如何返回 request
呢,下面 👇 就是一个例子 🌰
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); } ) ); });
上面的代码里咱们定义了 fetch
事件,在 event.respondWith
里,咱们传入了一个由 caches.match
产生的 promise.caches.match
查找 request
中被 service worker
缓存命中的 response
。
若是咱们有一个命中的 response
,咱们返回被缓存的值,不然咱们返回一个实时从网络请求 fetch
的结果。
固然,我也可使用第三方库,例如:lishaoy.net 使用了 sw-toolbox。
sw-toolbox 使用很是简单,下面 👇 就是 lishaoy.net 的一个例子 🌰
"serviceWorker" in navigator ? navigator.serviceWorker.register('/sw.js').then(function () { navigator.serviceWorker.controller ? console.log("Assets cached by the controlling service worker.") : console.log("Please reload this page to allow the service worker to handle network operations.") }).catch(function (e) { console.log("ERROR: " + e) }) : console.log("Service workers are not supported in the current browser.")
以上是 注册 一个 service woker
"use strict"; (function () { var cacheVersion = "20180527"; var staticImageCacheName = "image" + cacheVersion; var staticAssetsCacheName = "assets" + cacheVersion; var contentCacheName = "content" + cacheVersion; var vendorCacheName = "vendor" + cacheVersion; var maxEntries = 100; self.importScripts("/lib/sw-toolbox/sw-toolbox.js"); self.toolbox.options.debug = false; self.toolbox.options.networkTimeoutSeconds = 3; self.toolbox.router.get("/images/(.*)", self.toolbox.cacheFirst, { cache: { name: staticImageCacheName, maxEntries: maxEntries } }); self.toolbox.router.get('/js/(.*)', self.toolbox.cacheFirst, { cache: { name: staticAssetsCacheName, maxEntries: maxEntries } }); self.toolbox.router.get('/css/(.*)', self.toolbox.cacheFirst, { cache: { name: staticAssetsCacheName, maxEntries: maxEntries } ...... self.addEventListener("install", function (event) { return event.waitUntil(self.skipWaiting()) }); self.addEventListener("activate", function (event) { return event.waitUntil(self.clients.claim()) }) })();
就这样搞定了 🍉 (具体的用法能够去 sw-toolbox 查看)
有的同窗就问,service worker
这么好用,这个缓存空间究竟是多大?其实,在 Chrome 能够看到,如图
能够看到,大概有 30G ,个人站点只用了 183MB ,彻底够用了 🍓
最后,来两张图
因为,文章篇幅过长,后续还会继续总结 架构 方面的优化,例如
以及,渲染 方面的优化,例如
以及,性能测试工具,例如