本次淘宝首页改版,虽已再也不支持 IE6 和 IE7 等低版本的古董浏览器,但依然存在多个影响首页性能的因素:css
网页性能衡量指标有不少,假若可以把握关键的几个,集中优化,性能天然也就上去了。前端
最能反映页面性能的一个指标是 FPS(frame per second),通常系统设定屏幕的刷新率为 60fps,当页面元素动画、滚动或者渐变时绘制速率小于 60,就会不流畅,小于 24 就会卡顿,小于 12 基本认定卡爆了。git
1 帧的时长约 16ms,除去系统上下文切换开销,每一帧中只留给咱们 10ms 左右的程序处理时间,若是一段脚本的处理时间超过 10ms,那么这一帧就能够被认定为丢失,若是处理时间超过 26ms,能够认定连续两帧丢失,依次类推。咱们不能容忍页面中屡次出现连续丢失五六帧的状况,也就是说必须想办法分拆执行时间超过 80ms 的代码程序,这个工做并不轻松。github
页面在刚开始载入的时候,须要初始化不少程序,也可能有大量耗时的 DOM 操做,因此前 1s 的必要操做会致使帧率很低,咱们能够忽略。固然,这是对 PC 而言,Mobile 内容少,不管是 DOM 仍是 JS 脚本量都远小于 PC,1s 可能就有点长了。web
DOM 加载而且解析完成才会触发 DOMContentLoaded 事件,假若源码输出的内容过多,客户端解析 DOM 的时间也会响应加长,不要小看这里的解析时间,若是 DOM 数量增长 2000 个而且嵌套层级较深,解析时间也会相应增长 50-200ms,这个消耗对大多数页面来讲实际上是不必的,保证首屏输出便可,后续的内容只保留钩子,利用 JS 动态渲染。算法
Load 时间能够用来衡量首屏加载中,客户端接受的信息总量,若是在首屏中充满了大尺寸图片或者客户端与后端创建链接次数较多,Load 时间也会相应被拖长。json
流畅度是对 FPS 的视觉反馈,FPS 值越高,视觉呈现越流畅。为了保障页面的加载速度,不少内容不会在页面打开的时候所有加载到客户端。这里提到的流畅度是等待过程当中的视觉缓冲,以下方是 Google Plus 页面的一个效果图:后端
因为平台限制,淘宝首页面临一个先天的性能缺陷,首屏的渲染须要从 7 个不一样的后端取数据,这些数据请求是难以合并的,若是用户屏幕比较大,则首屏的面积也比较大,对应的后端平台数据接口就更多。数据是个性化内容或者为广告内容,故请求也不能缓存。浏览器
不论用户首屏的面积有多大,保证关键模块优先加载。下面代码片断是初始化全部模块的核心部分:缓存
$('.J_Module').each(function(mod) { var $mod = $(mod); var name = $mod.attr('tms'); var data = $mod.attr('tms-data'); if($mod.hasClass('tb-pass')) { Reporter.send({ msg: "跳过模块 " + name }); return; } // 保证首屏模块先加载 if (/promo|tmall|tanx|notice|member/.test(name)) { window.requestNextAnimationFrame(function(){ // 最后一个参数为 Force, 强制渲染, 不懒加载处理 new Loader($mod, data, /tanx/.test(name)); }); } else { // 剩下的模块进入懒加载队列 lazyQueue.push({ $mod: $mod, data: data, force: /fixedtool|decorations|bubble/.test(name) }); } }); 复制代码
TMS 输出的模块都会包含一个 .J_Module 钩子,而且会预先加载 js 和 css 文件。
对于无 JS 内容的模块,会预先打上 tb-pass 的标记,初始化的时候跳过此模块;对于首屏模块关键模块,会直接进入懒加载监控:
// $box 进入浏览器视窗后渲染 // new Loader($box, data) -> datalazyload.addCallback($box, function() { self.loadModule($box, data); }); // $box 当即渲染 // new Loader($box, data, true) -> self.loadModule($box, data); 复制代码
除必须当即加载的模块外,关键模块被加到懒加载监控,缘由是,部分用户进入页面就可能急速往下拖拽页面,此时,不必渲染这些首屏模块。
非关键模块统一送到 lazyQueue 队列,没有基于将非关键模块加入到懒加载监控,这里有两个缘由:
那么,何时开始加载非关键模块呢
var __lazyLoaded = false; function runLazyQueue() { if(__lazyLoaded) { return; } __lazyLoaded = true; $(window).detach("mousemove scroll mousedown touchstart touchmove keydown resize onload", runLazyQueue); var module; while (module = lazyQueue.shift()) { ~function(m){ // 保证在浏览器空闲时间处理 JS 程序, 保证不阻塞 window.requestNextAnimationFrame(function() { new Loader(m.$mod, m.data, m.force); }); }(module); } } $(window).on("mousemove scroll mousedown touchstart touchmove keydown resize onload", runLazyQueue); // 担忧未触发 onload 事件, 5s 以后执行懒加载队列 window.requestNextAnimationFrame(function() { runLazyQueue(); }, 5E3); 复制代码
上面的代码应该十分清晰,两种请求下会开始将非关键模块加入懒加载监控:
若是说上面的优化叫作懒加载,那么这里的优化能够称之为懒执行。
首页上有几个模块是包含交互的,如头条区域的 tab ,便民服务的浮层和主题市场的浮层,部分用户进入页面可能根本不会使用这些功能,因此程序上并无对这些模块作完全的初始化,而是等到用户 hover 到这个模块上再执行所有逻辑。
首屏中有两个次要请求,一个是主题市场的 hot 标,将用户最常逛的三个类目打标;第二个是我的中心的背景,不一样的城市会展现不一样的背景图片,这里须要请求拿到城市信息。
这两处的渲染策略都是,在程序的 idle(空闲)时期,或者 window.onload 十秒以后去请求,而后将请求的结果缓存到本地,当用户第二次访问淘宝首页时可以看到效果。这是一种更懒的执行,用户刷新页面才看获得.这种优化是产品可以接受,也是技术上合理的优化手段。
不论图片连接的来源是运营填写仍是接口输出,都难以保证图片具有恰当的宽高,加上现在 retina 的屏幕愈来愈多,对于这种用户也要提供优质的视觉体验,图片这块的处理并不轻松。
<img src='//g.alicdn.com/s.gif' data-src='//g.alicdn.com/real/path/to/img.png' /> 复制代码
阿里 CDN 是支持对图片尺寸作压缩处理的,以下图为 200x200 尺寸的图片:
$img = '//g.alicdn.com/real/path/to/img.png_400x400.jpg'; <img src='{{$img}}_100x100jpg_.webp' /> 复制代码
上面这种状况,图片是不会正确展现的。首页对全部的图片的懒加载都作了统一的函数处理:
src = src.replace(/\s/g, ''); var arr; if (/(_\d{2,}x\d{2,}\w*?\.(?:jpg|png)){2,}/.test(src) && src.indexOf('_!!') == -1) { arr = src.split('_'); if (arr[arr.length - 1] == '.webp') { src = [arr[0], arr[arr.length - 2], arr[arr.length - 1]].join('_'); } else { src = [arr[0], arr[arr.length - 1]].join('_'); } } if (src.indexOf('_!!') > -1) { src = src.replace(/((_\d{2,}x\d{2,}[\w\d]*?|_co0)\.(jpg|png))+/, '$1'); } WebP.isSupport(function(isSupportWebp) { // https 协议访问存在问题 IE8,去 schema if (/^http:/.test(src)) { src = src.slice(5); } // 支持 webp 格式,而且 host 以 taobaocdn 和 alicdn 结尾,而且不是 s.gif 图片 if (isSupportWebp && /(taobaocdn|alicdn)\.com/.test(src) && (src.indexOf('.jpg') || src.indexOf('.png')) && !/webp/.test(src) && !ignoreWebP && !/\/s\.gif$/.test(src)) { src += '_.webp'; } $img.attr('src', src); }); 复制代码
TMS 的模块在输出的时候会将数据的 id 放在钩子上:
若是模块是异步展现的,能够经过 tms-datakey 找到模块数据,而首页的个性化是从几十上百个模块中经过算法选出几个,若是把这些模块钩子所有输出来,虽然说取数据方便了不少,却存在大量的冗余,对此的优化策略是:将数据格式相同的模块单独拿出来,新建页面做为数据页。因此能够在源码中看到好几段这样的配置信息:
<textarea class="tb-hide">[{"backup":"false","baseid":"1","mid":"222726","name":"iFashion","per":"false","tid":"3","uid":"1000"},{"backup":"false","baseid":"3","mid":"222728","name":"美妆秀","per":"false","tid":"3","uid":"1001"},{"backup":"false","baseid":"4","mid":"222729","name":"爱逛街","per":"false","tid":"4","uid":"1002"},{"backup":"false","baseid":"2","mid":"222727","name":"全球购","per":"false","tid":"4","uid":"1003"}]</textarea> 复制代码
减小了大量的源码以及对 DOM 的解析。
有一些模块数据是不多被修改的,好比接口的兜底数据、阿里 APP 模块数据等,能够经过调整参数,设置模块的缓存时间,如:
io({ url: URL, dataType: 'jsonp', cache: true, jsonpCallback: 'jsonp' + Math.floor(new Date / (1000 * 60)), success: function() { //... } }); 复制代码
Math.floor(new Date / (1000 * 60)) 这个数值在一分钟内是不会发生变化的,也就是说将这个请求在本地缓存一分钟,对于低频修改模块,缓存时间能够设置为一天,即:
Math.floor(new Date / (1000 * 60 * 60 * 24))
复制代码
固然,咱们也能够采用本地储存的方式缓存这个模块数据:
offline.setItem('cache-moduleName', JSON.stringify(data), 1000 * 60 * 60 * 24); 复制代码
缓存过时时间设置为 1 天,淘宝首页主要采用本地缓存的方式。
这方面的优化不是不少,可是也有一点效果,不少模块的展现并非干巴巴的 .show(),而是经过动画效果,缓动呈现,这方面的优化推荐使用 CSS3 属性去控制,性能消耗会少不少。
页面优化的切入点不少,咱们不必定可以面面俱到,可是对于一个承载较大流量的页面来讲,下面几条必须有效执行:
固然,性能优化的切入角度不只仅是上几个方面,对照 Chrome 的 Timeline 柱状图和折线图,咱们还能够找到不少优化的点,如:
在优化的过程当中须要更多地思考,如何让阻塞的脚本分批执行,如何将长时间执行的脚本均匀地分配到时间线上。这些优化都体如今代码的细节上,宏观上的处理难以有明显的效果。固然,在宏观上,淘宝首页也有一个明显的优化:
// https://gist.github.com/miksago/3035015#file-raf-js (function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function(id) { clearTimeout(id); }; } })(); 复制代码
这段代码基本保证每一个模块的初始化都是在浏览器空闲时期,减小了不少没必要要的丢帧。这个优化也能够被应用到每一个模块的细节代码之中,不过优化难度会更高。
代码的性能优化是一个精细活,若是你要在一个庞大的未经优化的页面上作性能优化,可能会面临一次重构代码。本文从淘宝首页个性化引出的问题出发,从微观到宏观讲述了页面的优化实践,提出了几条能够借鉴的优化标准,但愿对你有所启发。优化的细节点描述的不够完善也不够全面,可是都是值得去优化的方向。
转载地址:fed.taobao.org/blog/2016/0…