页面性能优化的几个方面
页面性能的问题对用户体验的影响非常大,而现在移动端流量的提升给页面性能优化带来了更多的挑战,因为相同的页面从pc端移到手机上后,性能的问题都会被更明显的感受到。
要知道如何优化页面性能,就要知道页面的耗时到底在哪里。
一、对服务器的请求
一个页面内过多的http请求会导致页面响应速度下降,还可能阻塞页面逻辑的执行。
下图为http请求处理流程:
注:后经过考证,tcp的这种连接方式仅限于http1.0,http1.1支持长连接,也就是说一次tcp连接中可以有多次发送/响应的流程
对服务器发送的请求主要分为两种,一种是请求静态资源,一种是调用接口。 后者暂且不提,因为涉及到后端的业务逻辑。对于前者,一般有这样几条途径:
- 减少请求的数量
- 减少请求单次传递的数据量
- 均摊请求的发送
- js的异步加载
处理这个问题可以用到很多工具:比如requirejs,grunt或者gulp等工具。
requirejs
requirejs很强大,甚至可以说是它开创了javascript新时代。它是AMD(Asynchronous Model Definition)规范的一种被使用较多的实现。它采用异步的方式加载模块,在加载模块的时候不会影响后续代码的执行。
除了异步之外,用requirejs还可以很容易地做到按需加载,保证js文件只在被需要的时候加载。
define('CardData', ['Util'], function (Util) {
var ajaxUrlHead = Util.getAjaxHead();
var CardData = function() {};
var queryApi = {
getCardsInfo : {
url : "queryCardInfo",
params: {
}
}
};
CardData.prototype = {
getCardsInfo : function(p) {
var obj = queryApi.getCardsInfo;
$.extend(p.data, obj.params);
return Util.fnGetRequest(ajaxUrlHead + obj.url, p);
}
};
return new CardData;
});
上段代码中,define()定义了一个新的模块CardData, [“Util”]表示这个模块依赖的所有其他模块,CardData依赖Util。这个概念很像java里面的import。
define("name", [array], function(){ ... });
define函数表示在加载“name”模块之前,要先加载在array中的其他模块,加载完成之后,自动执行后面的回调函数。
注:requirejs的细节以后会有专门文章涉及
静态文件合并压缩
在这之前,其实要首先思考一下项目是不是选择了最合适的框架。
比如,一个功能较简单的移动端应用,能用Zepto,为什么还要用jquery呢?能用backbone,为什么还要用angular呢? 更有一些程序员喜欢为了实现一个效果,图省事直接引入一大包js库。。这。。像这样的问题,其实应该在项目开始的时候就要考虑而不是到后期再进行大幅的修改。
。。。
js文件的压缩通常非常有效,随便一个js文件一般都能被压缩掉50%左右的大小。压缩工具有很多。尝试过gulp-uglify,效果不错
gulp
gulp是一个前端构建工具。特色有:
- 学习成本低:gulp只有5个核心api
- 效率高:而且基于pipeline的概念,不需要写中间文件
- 社区强大:插件功能齐全,没有你找不到。每个插件都只完成一个功能,通过pipeline进行整合
- 安装简单:npm install
如下是一段简单的示例:
var gulp = require("gulp");
var RevAll = require("gulp-rev-all");
gulp.task("default", function() {
var revAll = new RevAll({
//哪些文件不需要重命名
dontRenameFile: [/js\/config\/.+\.js$/g, ".html"],
//哪些文件的引用不需要替换
dontUpdateReference: [/js\/config\/.+\.js$/g]
});
var afterRevision = gulp.src('src/**')
.pipe(revAll.revision());
afterRevision.pipe(gulp.dest("release"));
//为每个文件生成hash值
afterRevision.pipe(revAll.versionFile())
.pipe(gulp.dest("release"));
//rev-version.json 生成总版本hash和timestamp
afterRevision.pipe(revAll.manifestFile())
.pipe(gulp.dest("release"));
//rev-manifest.json 生成文件的对应引用
});
gulp-rev-all是一个插件。pipe可以说是gulp最大的特色。
静态资源的合并:通常会把web应用当中一些框架性的代码和库整合起来,(也就是每个页面都要加载的静态文件的集合)。过多的合并反而会导致页面加载量上升,这个度要根据项目的层次结构来把握。
二、页面的渲染
减少dom操作
dom操作是非常耗时的,(dom操作和页面渲染的原理还没有理清,等理清了会整理出来),所以页面中要尽可能的减少dom的操作。
举一个例子:
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i);
list.appendChild(item);
}
上面这段代码对list这个dom元素执行appendChild次数为item.length。基于减少dom操作的原则,我们先将list对象缓存,然后一次性添加。如下:
var fragment = document.createDocumentFragment();
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i);
fragment.appendChild(item);
}
list.appendChild(fragment);
事实上,对于这个问题,我们可以使用html模版来得出更好的解决方案。
用过underscore.js的微模版,感觉很好用。
render: function() {
var _this = this;
require(["text!path/to/file.tpl.html"], function (template){
var html = _.template(template)();
_this.$el.append(html);
});
},
上段代码中用require加载了一个html模版文件,回调函数的入参template就是该模版的返回值(一个字符串)。
_.template(template)(obj)
obj是可以传入的参数,为模版提供所需的数据。
obj = {
data: {
title: "list",
items: ["a","b","c"]
}
}
<h5><%=data.title%></h5>
<ul>
<% for (var i = 0; i < data.items.length; i++) { %>
<li><%=data.items[i]%></li>
<% }%>
</ul>
解析后的结果是:
<h5>list</h5>
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
结果是将模版内的所有dom操作合并成了一次,而且从另一个角度来说,也提升了前端页面的可配置性。不同的业务流程调用不同的组件,不同的组件可以调用不同的模版,而不需要再hardcode一些if/else判断了。
开启css硬件加速
web应用中,页面的渲染速度一直都无法与native应用相提并论,其根本原因是native应用总是可以很好的运用GPU,这是为什么它比web应用表现更好的原因。一些效果,特别是css动画效果,对于移动设备还是有一定的压力的。现在大多数电脑的显卡都支持硬件加速。鉴于此,我们可以发挥GPU的力量,从而是我们的网站或应用表现的更为流畅。硬件加速在移动端尤其有用,因为它可以有效的减少资源的利用。
CSS animations, transforms以及transitions不会自动开启GPU加速,而是由浏览器的缓慢的软件渲染引擎来执行,但大多数浏览器都提供了某些触发硬件加速的CSS规则,最显著的是元素的3d变换。
但我们可能不想对元素应用3D变换,可我们一样可以开启3D引擎。比如可以用transform: translateZ(0)来开启。
.cube {
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
}
再webkit内核的浏览器中,另一个行之有效的方法是
.cube {
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
这就是为什么我们看到很多新的库当中都会有translate3d(0,0,0)的原因。
我对前端的理解还不够深入,以后有新的了解会持续更新。