译者:季公亥
转贴镜像:
segmentfault.com/a/1190000015052545序言
有关 操控性强化 是个大的面,这首诗主要就牵涉到 后端 的两个点,如 后端操控性强化 的业务流程、常用管理手段、辅助工具等。
提到 前端操控性强化 ,他们如果单厢想不到 AOL赵雄,责任编辑会紧密结合 AOL赵雄 带入他们的介绍科学知识,展开的归纳和剖析 。
AOL赵雄
具体来说,他们先来看一看“AOL赵雄”的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打包组件成复合文本如对 AOL赵雄的具体细则文本不是很介绍,可自行访问这篇优质文章:后端操控性强化之AOL35条赵雄 介绍详情。
填充 合并
对于 后端操控性强化首屏打开速度,而这个速度,很大因素是花费在互联网允诺上,那么怎么增加互联网允诺的时间呢?
增加互联网允诺单次减小文档体积采用 CDN 加速所以填充、合并就是一个解决方案,当然可以用 gulp 、 webpack 、 grunt 等构建辅助工具填充、合并。
JS、CSS 填充、合并
例如:gulp js、css 填充、合并代码如下 :
//填充、合并jsgulp.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/));});// 填充、合并 CSSgulp.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,看一看效果如何:
以上是 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图片填充代码如下 :
//填充imagegulp.task(imagemin, function () { gulp.src(./public/**/*.{png,jpg,gif,ico,jpeg}) .pipe(imagemin()) .pipe(gulp.dest(./public));});图片的合并可以采用 CSSSpirite,方法就是把一些小图用 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 个方面提升操控性。
增加响应延迟,提升响应时间增加互联网带宽消耗,节省流量他们用两幅图来介绍下浏览器的 内存机制。
1、浏览器第一次允诺
2、浏览器再次允诺
从以上两幅图中,可以清楚的介绍浏览器 内存 的过程:
首次访问一个 URL ,没有 内存 ,但是,服务器会响应一些 header 信息,如: expires、cache-control、last-modified、etag 等,来记录下次允诺是否内存、如何内存。再次访问这个 URL 时候,浏览器会根据首次访问返回的 header 信息,来决策是否内存、如何内存。他们重点来分析下第二幅图,其实是分两条线路,如下 。
第一条线路: 当浏览器再次访问某个 URLheaderheader信息 (允诺不会和服务器通信) ,也就是 强内存 ,如图:
第二条线路: 如没有命中 强内存 ,浏览器会发送允诺到服务器,允诺会携带第一次允诺返回的有关内存的 header信息 (Last-Modified/If-Modified-Since和Etag/If-None-Match) ,由服务器根据允诺中的相关header 信息来比对结果是否协商内存命中;若命中,则服务器返回新的响应 header信息更新内存中的对应header协商内存。
现在,他们介绍到浏览器内存机制分为 强内存、协商内存,再来看一看他们的区别 :
与强内存相关的header 字段有两个:
1、expires
expires: 这是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,如 Mon,10Jun201521:31:12GMT,如果发送允诺的时间在expires
2、cache-control
cache-control:max-age=number ,这是 http1.1时出现的header 信息,主要就是利用该字段的 max-age 值来展开判断,它是一个相对值;资源第一次的允诺时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的允诺时间比较,如果允诺时间在过期时间之前,就能命中内存,否则未命中,cache-control 除了该字段外,还有下面两个比较常用的设置值:
no-cache:不采用本地内存。需要采用内存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag ,那么允诺的时候会与服务端验证,如果资源未被更改,则可以防止重新下载。no-store:直接禁止浏览器内存数据,每次用户允诺该资源,单厢向服务器发送一个允诺,每次单厢下载完整的资源。public: 可以被所有的用户内存,包括终端用户和 CDN 等中间代理服务器。private: 只能被终端用户的浏览器内存,不允许 CDN 等中继内存服务器对其内存。Tips:如果 cache-control 与 expires 同时存在的话,cache-control 的优先级高于 expires。
协商内存
协商内存都是由浏览器和服务器协商,来确定是否内存,协商主要就通过下面两组header 字段,这两组字段都是成对出现的,即第一次允诺的响应头带上某个字段 (Last-Modified或者 Etag ) ,则后续允诺会带上对应的允诺字段 (If-Modified-Since 或者 If-None-Match ) ,若响应头没有 Last-Modified 或者 Etag字段,则允诺头也不会有对应的字段。
1、Last-Modified/If-Modified-Since
二者的值都是 GMT格式的时间字符串,具体过程:
浏览器第一次跟服务器允诺一个资源,服务器在返回这个资源的同时,在 respone 的 header 加上 Last-Modified字段,这个 header字段表示这个资源在服务器上的最后修改时间。浏览器再次跟服务器允诺这个资源时,在 request 的 header 上加上 If-Modified-Since 字段,这个 header 字段的值就是上一次允诺时返回的 Last-Modified的值。服务器再次收到资源允诺时,根据浏览器传过来 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回 304NotModified,但是不会返回资源文本;如果有变化,就正常返回资源文本。当服务器返回304NotModified 的响应时, response header 中不会再添加 Last-Modified的header ,因为既然资源没有变化,那么 Last-Modified 也就不会改变,这是服务器返回 304 时的 response header。浏览器收到 304 的响应后,就会从内存中加载资源。如果协商内存没有命中,浏览器直接从服务器加载资源时,Last-Modified 的 Header 在重新加载的时候会被更新,下次允诺时,If-Modified-Since 会启用上次返回的Last-Modified 值。2、Etag/If-None-Match
这两个值是由服务器生成的每个资源的唯一标识字符串,只要资源有变化就这个值就会改变;其判断过程与Last-Modified、If-Modified-Since 类似,与 Last-Modified 不一样的是,当服务器返回 304NotModified 的响应时,由于 ETag 重新生成过, response header中还会把这个 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 本来就支持的功能,这也是相比于web app,原生app 更受青睐的主要就原因。
再来看一看 service worker 能做些什么:
后台消息传递互联网代理,转贴允诺,伪造响应离线内存消息推送…责任编辑主要就以(lishaoy.net)资源内存为例,阐述下 service worker如何工作。
2、生命周期
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 ,如图 :现在,他们来写个简单的例子 。
3、注册 service worker
要安装service worker ,你需要在你的页面上注册它。这个步骤告诉浏览器你的 service worker 脚本在哪里。
if (serviceWorker innavigator) { 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 已经被注册过,浏览器会自动忽略上面的代码。
4、激活 service worker
在你的 service worker 注册之后,浏览器会尝试为你的页面或站点安装并激活它。
install 事件会在安装完成之后触发。install事件一般是被用来填充你的浏览器的离线内存能力。你需要为install 事件定义一个 callback ,并决定哪些文档你想要内存.
// The files we want to cachevar 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并采用它来获得安装耗费的时间以及是否安装成功。
5、监听 service worker
现在他们已经将你的站点资源内存了,你需要告诉 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; } returnfetch(event.request); } ) );});上面的代码里他们定义了 fetch 事件,在 event.respondWith 里,他们传入了一个由 caches.match产生的 promise.caches.match 搜寻 request 中被 service worker 内存命中的 response 。
如果他们有一个命中的 response ,他们返回被内存的值,否则他们返回一个实时从互联网允诺 fetch 的结果。
6、sw-toolbox
当然,我也可以采用第三方库,例如: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”; varstaticImageCacheName =“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()) })})();就这样搞定了 (具体的用法可以去
https://googlechromelabs.github.io/sw-toolbox/api.html#main 查看)。有的同学就问,service worker 这么好用,这个内存空间到底是多大?其实,在Chrome可以看到,如图:
可以看到,大概有 30G ,我的站点只用了 183MB ,完全够用了 。
最后,来两张图:
由于,文章篇幅过长,后续还会继续归纳架构 方面的强化,例如:
bigpipe分块输出bigrender分块渲染…以及,渲染 方面的强化,例如:
requestAnimationFramewell-change硬件加速 GPU…以及,操控性测试辅助工具,例如:
PageSpeedaudits…译者:季公亥
转贴镜像:
segmentfault.com/a/1190000015052545