效率和开始的节奏以及功能的丰富彼此相互制约。最近忙着做功能,明知道有些地方可以优化的也得先放放,但是老大一关注,你的马上去做。
在老大的眼中,一次优化好后就可以不再优化,或者只需要很少的时间来维护,却不知道,优化是一个持续的过程,想法赶不上变化。
做人难,做事情更难!
优化如何开始,怎么开始,以及做些什么还是值得思量的。
优化的一些准则:
1 优化是持续存在的,当你开始做一个功能的时候,优化就开始了。而我们往往认为新做的功能就是一个优化好的功能,殊不知这个功能或者会影响到其他的功能,
或者先把功能做到70%,稍后再去优化也不迟。
2 不到万不得已的时候不要去优化。用户对于性能是有一定的忍耐性的,有时一个小小的加载图标就能让用户感到很满足的。
3 优化时需要写下用例来驱动,什么条件下测试的结果如何,有了测试用例,我们才能做到有的放矢;优化时需要保存系统的一个快照;有了这些历史信息我们才能更好的去对比,去寻找不足和需要继续优化的地方。
4 优化需要针对的是系统的瓶颈所在,找到系统的性能瓶颈,攻破他,我们得到的是事半功倍的效果。
对于一个网站的页面优化又该从何入手呢?
一个web网站的页面的生命周期有3个阶段:
1 加载
2 渲染
3 响应用户的交互
针对这三个阶段需要采取不同优化策略:
1 对于加载阶段:
加载的核心是网络传输,因此重点就是将页面折腾到最小、放到离用户最近的地方,这样加载就快了。具体的实施细则可以参考:<a href="http://developer.yahoo.com/performance/ ">yahoo的14条建议</a><a href="http://msdn2.microsoft.com/en-us/library/ms533020.aspx#Close_Your_Tags">
摘要如下:
折腾小的话,需要将页面中无用的信息去掉,gzip压缩。
1)采用</a><a href="http://developer.yahoo.com/yui/compressor/ ">yui compressor</a>来进行压缩css和js等
2)合并js和css文件,使用jsmin等工具
3)压缩图片,合并背景图。常用工具<a href="http://pmt.sourceforge.net/pngcrush/ ">PngCrush</a>、<a href="http://psydk.org/PngOptimizer.php ">PngOptimizer</a>。
4)延迟加载,等用户用到了相应的素材时才对素材进行加载。
离用户最近的话,那就是用户的浏览器cache了,所以要设置页面元素的尽量长的过期时间,能够cache的统统cache住,能够放到cdn的都放到cdn上去
2 对于渲染阶段:
渲染阶段的核心是让用户尽快的看到页面的展现,能够与页面进行交互,因此重点是显示首屏的内容,让用户提早的能够看到页面,感受交互。
1 页面首屏先显示出来的话,需要将js代码的事件处理在dom树构建好之后,如同jquery的ready事件。
2 对于html中一些默认可以不关闭的标签,尽早的关闭掉,这样也有助于提高渲染的速度。如果让浏览器去判断何时关闭才是ok的,那需要不少的回溯操作,花费不少的代价。
<a href="http://msdn2.microsoft.com/en-us/library /ms533020.aspx#Close_Your_Tags">msdn上有详细的描述 Close Your HTML Tags </a>
Unlike XML, HTML has the notion of implicitly closed tags. This includes frame, img, li, and p. If you don't close these tags, Internet Explorer renders your pages just fine. If you do close your tags, Internet Explorer will render your pages even faster.
3 一些大图片的延迟加载。由于浏览器并发的连接有限,对于一些大图片会被block住,所以尽量的延迟加载,或者按需加载,渲染的速度也会快一些
4 将web服务器缓冲区中的内容尽早的输出,会提浏览器开始渲染的时间。
5 页面首屏的显示尽量不要依赖于js代码。这一点很重要,这样做的话,你的一些css和js就可以延迟加载进来,而不影响首屏的展示
6 页面onload的时候可以考虑做一些预加载的事情,把一些其它页面需要用到的素材预先加载进来。搜索引擎如google,yahoo都这样做的,悄悄的加载一些大的背景图进来。
<script>
window.onload = function () {
var script = document.createElement("script");
script.src = ...;
document.body.appendChild(script);
};
</script>
4 对于运行阶段:
运行阶段的核心是对js代码的优化,根据js代码的执行原理,写出高效的js代码来。
1)变量使用就近原则
js有作用域链的概念,如果本作用域找不到,就会到上一个作用域去找,如果过多的使用全局变量,就会导致每次使用变量都要去层层的检查各个作用域,性能花费不菲啊。
声明变量时记得要加上var,否则这个变量会被放到window这个顶级作用域下
不要使用with来遍历,它会阻止js执行器从本地来查找,而是遍历作用链来查找。
对于一些不得不用的全局变量,利用局部变量来缓存它
var arr = ...;
var globalVar = 0;
(function () {
var i;
for (i = 0; i < arr.length; i++) {
globalVar++;
}
})();
var arr = ...;
var globalVar = 0;
(function () {
var i, l, localVar;
l = arr.length;
localVar = globalVar;
for (i = 0; i < l; i++) {
localVar++;
}
globalVar = localVar;
})();
2)对于prototype链也是一样,尽量的避免prototype链的遍历
function A () {}
A.prototype.prop1 = 1;
function B () {
this.prop2 = 2;
} B.prototype = new A();
var b = new B();
alert(b.prop1);
从b读取属性prop1,就需要从b的prototype链遍历到a的ptototype链,想想如果链很长的话,结果会如何呢
3)还是prototype,如果要创建许多的对象,建议将对象的公共方法放到prototype中,而不是放到构造函数中。
由于prototype是公用的,构造函数是每个对象都需要初始化的,会节约不少的内存。
这个需要根据情况使用,通过prototype会增加对象成员的查找时间。
function Foo () {...}
Foo.prototype.bar = function () {...};
function Foo () {
this.bar = function () {...};
}
4)尽量不要使用eval,除非性能不是问题或者必须使用eval来解决问题。
同理在seTtimeout或者setInterval是最好使用匿名函数,使用字符串的话,会导致一次eval操作。
setTimeout(function () {
// 业务代码
}, 50);
5)字符串的连接操作,尤其实在循环中的连接操作,在ie的一些版本的浏览器上会导致频繁的临时变量的拷贝(如c语言中的realloc),建议使用Array对象的join方法来解决。
更多可以参考<a href="http://blog.goguoguo.com/html/y2009/142.html">javascript优化</a>
var i, s = “”;
for (i = 0; i < 10000; i++) {
s += “x”;
}
var i, s = [];
for (i = 0; i < 10000; i++) {
s[i] = “x”;
}
s = s.join(“”);
6)除非是为了动态构建正则表达式,尽量少的使用Regexp对象;而且为了判断一个字符串是否匹配,建议使用test而不是exec,exec有一些性能问题。
还有,正则表达式尽量的简单,如果复杂的话,想想来回的字符串匹配,复杂度提高不少。
7) 能够cache的地方尽量的使用cache,尤其使一些循环中使用到的大数据集,相对稳定,又只是大量的读操作。
//没有使用缓存,每次调用都要生成v
var fn = (function () {
var b = false, v;
return function () {
if (!b) {
v = ...;
b = true;
}
return v;
};
})();
//将v存储到fn中
function fn () {
if (!fn.b) {
fn.v = ...;
fn.b = true;
}
return fn.v;
}
//延迟加载
var fn = function () {
var v = ...;
return (fn = function () {
return v;
})();
};
7)对于一些大数据集的处理,尽量放到server中去做,js处理大数据集没有优势。
不要让js执行导致ui被阻塞住,这个效果就太差了。
尽量将长时间js代码通过分块在setTimeout中来做,每块不要超过300ms。否则用户就会有意见了。
function doSomething (callbackFn) {
// 做一些初始化的工作
(function () {
// 做一小块工作,时间不要长
if (termination condition) {
callbackFn();
} else {
// 执行下一个块
setTimeout(arguments.callee, 0);
}
})();
}
这和ajax的原理是一样的,多线程,分小块的去执行,提高ui交互的效率。
8) 原语操作比函数的性能要高很多。尽量使用原语操作
//小于操作
var a = 1, b = 2, c;
c = Math.min(a, b);
c = a < b ? a : b;
//数组操作
myArray.push(value);
myArray[myArray.length] = value;
myArray[idx++] = value;
9)少在循环或者性能关键点处使用异常处理,因为异常处理需要保存不好的堆栈信息。
//循环中使用try、cache不明智
var i;
for (i = 0; i < 100000; i++) {
try {
...
} catch (e) {
...
}
}
//将try、cache提取出来
var i;
try {
for (i = 0; i < 100000; i++) {
...
}
} catch (e) {
...
}
10)性能关键点处少使用for、in,with这种操作,因为涉及到prototype链的查找
var key, value;
for (key in myArray) {
value = myArray[key];
...
}
//换成数组来操作
var i, value, length = myArray.length;
for (i = 0; i < length; i++) {
value = myArray[i];
...
}
11)在if、else的分支中如果分支一样,在外面定义它
function fn () {
if (...) {
...
} else {
...
}
}
var fn;
if (...) {
fn = function () {...};
} else {
fn = function () {...};
}
12)操作dom树的时候使用innerHtml,一次性的构造,而不是append,每次还需要调整
不过使用innerHtml也有不少的危险,比如插入script钓鱼脚本等,更多参考
<a href="http://www.julienlecomte.net/blog/2007/12/38/"> The Problem With innerHTML</a>
var i, j, el, table, tbody, row, cell;
el = document.createElement("div");
document.body.appendChild(el);
table = document.createElement("table");
el.appendChild(table);
tbody = document.createElement("tbody");
table.appendChild(tbody);
for (i = 0; i < 1000; i++) {
row = document.createElement("tr");
for (j = 0; j < 5; j++) {
cell = document.createElement("td");
row.appendChild(cell);
}
tbody.appendChild(row);
}
var i, j, el, idx, html;
idx = 0;
html = [];
html[idx++] = "<table>";
for (i = 0; i < 1000; i++) {
html[idx++] = "<tr>";
for (j = 0; j < 5; j++) {
html[idx++] = "<td></td>";
}
html[idx++] = "</tr>";
}
html[idx++] = "</table>";
el = document.createElement("div");
document.body.appendChild(el);
el.innerHTML = html.join("");
13) 修改dom树时尽量使用clone,而不是动态的再创建。
至少可以省去不少初始化的时间
var i, j, el, table, tbody, row, cell;
el = document.createElement("div");
document.body.appendChild(el);
table = document.createElement("table");
el.appendChild(table);
tbody = document.createElement("tbody");
table.appendChild(tbody);
for (i = 0; i < 1000; i++) {
row = document.createElement("tr");
for (j = 0; j < 5; j++) {
cell = document.createElement("td");
row.appendChild(cell);
}
tbody.appendChild(row);
}
var i, el, table, tbody, template, row, cell;
el = document.createElement("div");
document.body.appendChild(el);
table = document.createElement("table");
el.appendChild(table);
tbody = document.createElement("tbody");
table.appendChild(tbody);
template = document.createElement("tr");
for (i = 0; i < 5; i++) {
cell = document.createElement("td");
template.appendChild(cell);
}
for (i = 0; i < 1000; i++) {
row = template.cloneNode(true);
tbody.appendChild(row);
}
14)使用documentfragment
var i, j, el, table, tbody, row, cell, docFragment;
docFragment = document.createDocumentFragment();
el = document.createElement("div");
docFragment.appendChild(el);
table = document.createElement("table");
el.appendChild(table);
tbody = document.createElement("tbody");
table.appendChild(tbody);
for (i = 0; i < 1000; i++) {
...
}
document.body.appendChild(docFragment);
15)事件绑定的时候尽量绑定到最少的页面元素的子集。
我们用jquery的选择器,常常一下子就选择了许多的元素,each一下,然后绑定事件处理函数。
如果选择出来的集合很大的话,不仅选择费时间,绑定事件处理操作是比较费时间的。
适当的时候可以借助事件的冒泡机制来实现事件的绑定。
要较快页面的显示,光有js的优化是不够的,css也需要进行相应的优化。
css优化点:
1)css sprite合并背景图
2)js不要参与到布局中来,这样的话,在没有js的情况下页面也能够正常的显示,
就可以吧js放到后米去加载了。
3)不要使用ie的表达式,不要难过使用ie的过滤器
4) 优化table的布局,让table能够在接收到全部数居前就可以进行渲染
Use table-layout:fixed
Explicitly define a COL element for each column
Set the WIDTH attribute on each col