JS性能优化技巧
让垃圾收集器销毁不必要的变量
解除变量的引用
局部变量本身可能会在函数执行完毕之后就会由垃圾收集机制来释放内存,但是全局变量始终不会被垃圾收集机制释内存。
但是在闭包的环境中,如果函数的变量被它内部的函数所引用的话同样也可能不会被垃圾收集机制释放内存。
举个栗子
function creat(){
var elements=document.getElementById('xxx');
element.onclick=function(){
alert(element.id);
}
}
这个栗子中即便已经执行完了creat函数,但是事件处理程序始终都在引用它的局部变量elements。
方法
将null赋值那些不能被垃圾收集机制回收的变量
注意
解除引用之后,只是让变量脱离了执行环境,并不能立即释放内存,只有当垃圾收集器下一次运行时才会释放内存。
用let声明变量
let是ES6中新添加的关键字,用于声明变量并将变量绑定到相应的块级作用域中。因为是块级作用域,当作用域中用let声明的变量执行完后,就会在被下一次的垃圾收集器所销毁。
减少DOM操作
事件委托
以前我们可能会为我们的表单添加多个onchange事件来异步地去验证用户输入的信息,如果这个表单的信息很长的多,那么我们添加的事件处理程序就随之增加。而这些都会增加对象(对象会占用内存),并且也会增加DOM的访问次数。为此可以利用事件冒泡的原理,将多个事件处理程序委托给一个上层元素,从而优化性能
举个栗子
html:
<ul id="test">
<li id="goSomeWhere">Go somewhere</li>
<li id="dosomething">Do something</li>
</ul>
css:
var list=document.getElementById("test");
EventUtil.addHandler(list,"click",function(event){
event=EventUtil.getEvent(event);
var target=Event.getTarget(event);
switch(target.id){
case : "doSomething":
document.title=”I changed the document's title";
break;
case "goSomewhere":
location.href="xxxxxx";
break;
}
});
访问集合元素的时候尽量使用局部变量
例如:经常我们在处理某个元素的子元素时,通常获取HTMLColletion,然后得到对于的数组长度对每个子元素进行操作。
var test=document.getElementTagName('ul');
for(var i=0;i<test.length;i++){
//处理程序
}
由于HTMLColletion是实时更新的,每一次循环都会重新进行DOM查询,而DOM操作是比较耗费资源的。
解决方法
用变量存储HTMLColletion对象的长度
var test=document.getElementTagName('ul');
for(var i=0,len=test.length;i<len;i++){
//处理程序
}
缓存布局信息
纯JS写动画的过程中,经常会多次访问元素的offsets偏移量,滚动位置等。例如把某个元素向右边移动。
function Animation(elem){
var speed=10;
function move(){
elem.style.left=elem.offsetLeft+speed+'px';
if(elem.offsetLeft<500){
setTimeout(function(){
move()
},100);
}
else{
elem.style.left=500+'px';
}
}
}
但是这个操作中,每次移动元素都会重新获取elem的偏移量,而JS引擎为了获得最新的偏移量,就会重新刷新队列。最好用局部变量存储某些信息,再进行操作,优化的代码如下:
function Animation(elem){
var speed=10;
var left=lem.offsetLeft;
function move(){
elem.style.left=left+speed+'px';
left=left+speed;
if(left<500){
setTimeout(function(){
move()
},100);
}
else{
elem.style.left=500+'px';
}
}
}
这样只用查询一次elem的left了,想比上面的栗子减少大量队列刷新的次数。
使用更加高效的DOM操作
childNodes、nextSibling
一般来说childNodes和nextSibling方法运行的时间几乎没有差别,但是在IE中nextSibling普遍比childNode性能更好。除了nextSibling之外类似的还有firstChild。如果页面要兼顾到老版本的IE,则建议使用nextSibling.children、childElementCount、firstElementChild、lastElementChild、nextElementSibling、nextElementSibling、previousElementSibling
这些DOM操作直接获取的是元素而不是节点,相比较childNotes这一系列的操作,它们省去了去检测节点是否是元素节点的过程,因此性能更好。如果能够使用这些方法的法,就尽量使用。
减少重绘和重排
DOM中每一个节点发生变化都会导致重绘,如果这个变化会影响到布局就会导致重排。重排和重绘是DOM性能开销比较大的主要原因。
思路
1、让元素脱了文档流(这样以后元素的改变都不会引起重绘和重排,虽然这里会引起一次重绘和重排)
2、改变脱离文档的元素
3、把元素带回文档当中
减少队列刷新
- offsetTop,offsetLeft,offsetWidth,offsetHeight
- scrollTop,scrollLeft,scrollWidth,scrollHeight
- clientTop,clientLeft,clientWidth,clientHeight
- getComputedStyle()(IE)
这些方法都会导致队列刷新,因为这些方法是获取当前最新的信息,所以要刷新队列,重新获取信息。尽量减少上面方法的使用次数。
减少全局变量污染
创建唯一的全局变量
将全局变量变成一个容器用来储存全局性的信息。这样所以的全局信息都在同一个命名空间下,可以大幅度降低与其他应用程序、组件或者类库之间发生冲突的可能性。
举个栗子
var ALL={};
ALL.person={
name:"test",
age:18
};
ALL.car={
name:"test"
}
模块化
通过函数和闭包来构造模块,模块是一个仅仅提供接口而不会显示状态与实现的函数或者对象。将功能模块化,几乎可以不使用全局变量。
实现思想
- 将这个模块所需要数据全部存储为局部变量
- 创建一个内部函数或者对象处理外面的局部变量并返回所需要的结果,再将这个函数返回。
举个栗子
var count=function(){
var math=0,
eng=0;
return{
add_math:function(score){
math=score;
},
add_eng:function(score){
eng=score;
},
show_score:function(){
console.log(eng+math);
}
}
};
var test=count();
test.add_math(100);
test.add_eng(100);
test.show_score();//200
这个实现成绩统计的模块由于所有的变量都在它自己的作用域中,大幅度降低与其他应用程序、组件或者类库之间发生冲突的可能性。
记忆
通常在递归计算中,存在着大量的重复计算。可以通过记忆这种技巧来存储之前计算出的数据从而减少计算次数或者函数调用次数优化性能。
比如说阶乘运算
普通的函数调用方法
function factorial(n){
if(n>1){
return n*factorial(n-1);
}
if(n=1){
return n;
}
};
for(var i=1;i<5;i++){
console.log(factorial(i));
}
函数factorial被调用了15次,其中重复的调用就有10次。如果数值再大点的话,就会发现这样对性能还是蛮有影响的。
采用记忆技巧
function memory(memo,func){
var recur=function(n){
var result=memo[n];
if(typeof result !=='number'){
result=func(recur,n);
alert(result);
memo[n]=result;
}
return result;
};
return recur;
}
function factorial(recur,n){
if(n>1){
return n*recur(n-1);
}
if(n=1){
return n;
}
};
var test=memory([1],factorial);
for(var i=1;i<5;i++){
console.log(test(i));
}
这样factorial只需被调用5次
减少动态生成的代码
动态生成的代码,一般都是把代码变成字符串的形式传入到某些参数中。例如:with,eval()、setTimeout(“code”,time),setInterval(“code“,time)…因为这些代码被引擎执行的时候,它不会立刻就知道这都这些代码到底是干什么的以及这些代码对作用域如何进行修改,也无法知道传递给with用来创建新作用域的对象的内容到底是什么。引擎会先解析这些字符串,再执行代码,这无疑会让运行变慢。如果过多地去动态生成代码,就会对JS性能造成较大影响。因为静态生成的代码在编译阶段就已经确定了它的位置和作用域,引擎只需要直接执行代码即可。
尽量使用===
通常我们理解==、===的是:==是值相等,===是类型和值均相等。但是实际上,==允许的在等比较中进行强制类型转换(尽量用强制转换让它们相等),而===则是不允许的。因此==实际上会比===多一部分操作,虽然这多一点的操作对JS性能来说可能影响不是特别大,但是既然是优化性能,因此要将其性能最大化。
数据操作拆分执行
随便说个栗子,假如我们要通过Ajax获取数据,然而这个数据特别的大,比如上万条的评论信息,如果要一次处理完这所以的数据恐怕会花费大量的时间,而这段之间内用户却什么都干不了,这时候他们就可能以为页面卡住了而重新刷新页面,这种用户体验是十分糟糕的。因此我们其实可以通过循环的超时调用来分批处理数据。
减少HTTP请求
尽量把多个JS文件合成一个
尽量用一个链接去请求多个文件。
尽量减少对对象属性的访问
对象属性的访问相比直接访问字面量或者变量来说,所带来的花销更大,为此要减少一些不必要的对象属性访问。
举个栗子
function test(elem,class1,class2){
console.log(elem.className==class1);
console.log(elem.className==class2);
}
这里elem的className属性被访问了两次,而className属性是不变了,为此我们可以用局部变量存储这个属性值,从而减少对对象属性的访问次数。
减少访问的深度
就直接拿全局变量和局部变量开涮,全局变量始终是最后才能访问到的变量,而当前执行环境的局部变量始终是开始就能访问的变量,两者间的性能开销不言而喻。
举个栗子
function test(){
var obj1=doucment.getElementById('test1'),
obj2=doucment.getElementById('test2'),
obj3=doucment.getElementById('test3');
}
三次都是在访问全局对象document,不妨像下面这样
function test(){
var doc=document,
obj1=doc.getElementById('test1'),
obj2=doc.getElementById('test2'),
obj3=doc.getElementById('test3');
}
这样访问全局对象只访问了一次而已。
优化循环操作
减少循环中不必要的操作的次数
减少对象成员以及数组项的查找次数。例如访问某些HTML集合(因为每次都会重新查询)的时候,可以存储到变量中。
尽量少地使用for-in语句
for-in语句相比for、while等等这些来说,性能开销会更大,因为它的每次迭代操作都会搜索实例和原型中的属性。而其他的循环则不会搜索原型。
颠倒循环顺序
颠倒循环顺序能够在每次迭代的过程中减少一次数值比较操作。
for(var i=objets.length;i--;){
...
}
增加每次循环的操作来减少循环次数
每次循环都会带来额外的性能开销,即便迭代的时候没有执行任何操作,但是也会损耗性能。可以让一次循环就执行多个循环的操作,从而减少循环次数。
var listNum=Math.floor(item.length/8),
stadtAt=item.length%8,
i=0;
do{
switch(startAt){
case 0:process(item[i++]);
case 7:process(item[i++]);
case 6:process(item[i++]);
case 5:process(item[i++]);
case 4:process(item[i++]);
case 3:process(item[i++]);
case 2:process(item[i++]);
case 1:process(item[i++]);
}
startAt=0;
}while(--listNum);
if-else与switch
当条件增加的时候,if-else性能负担增加的程度比switch要大,因为为了使得性能最佳,在判断两个离散值或者几个不同的值域的时候用if-else,多于2个离散值最好使用switch。
优化if-else
对于多个不同的值域的时候,应该把最可能出现的值域放在前面,同时也要尽可能地减少到达正确条件前条件判断的次数。
举个栗子
if(value<6){
if(value>3){
if(value==0){
}
else if(value==1){
}
else{
}
}
else{
if(value==3){
}
else if(value==4){
}
else{
}
}
}
else{
if(value<8){
if(value==6){
}
else {
}
}
else{
if(value==8){
}
else if(value==9){
}
else{
}
}
}
优化字符串操作
减少concat的使用
大多数情况下,concat要比简单地使用+和+=稍慢,尤其是在IE、Opera和Chrome中慢的更加明显。
避免临时字符串(IE7及下除外)
在字符串合并的过程中,游览器会尝试为表达试左侧的字符串分配更多的内存,然后简单地将第二个字符串拷贝到它的末尾。如果基础字符串位于最左端的位置,就可以避免重复拷贝一个逐渐变大的基础字符串。
举个栗子
var str1 = 'abcdef',
srr2 = 'ab',
str3 = 'c';
// 低性能
var str = str3 + str2 + str1;
// 高性能
var str = str1 + str2 + str1;