Javascript中的四种循环:for循环、while循环、do-while循环、for-in循环。for、while和do-while循环性能特相差不大,其中只有for-in循环要明显慢一些(1/7),因为他需要同时搜索对象的实例和原型属性。因此,除非明确需要遍历属性数未知的对象,否则因避免使用for-in循环(用for循环或查找表代替)。
var props = ["prop1", "prop2"],i = 0; while (i < props.length){ process(object[props[i]]); }
改善循环性能的最佳方式:减少每次迭代的运算量;减少每次循环的迭代次数。
//原始循环 for (var i=0; i < items.length; i++){ process(items[i]); }
每次循环体(迭代)需要执行的操作:
1.用变量缓存需要的对象或者数组成员:减少对数组项或对象成员的查找次数,优化上面的第一步操作。(提升25%、ie中50%)
//减少属性查询 for (var i=0, len=items.length; i < len; i++){ process(items[i]); }
2.颠倒数组的循环顺序,从后往前:减少比较次数,从2次比较(是否小于总数、是否为true)较少到一次(是否为true),优化了上面的第二步操作。(提升50%-60%)
//减少属性并颠倒顺序 for (var i=items.length; i--; ){ process(items[i]); }
现在每次循环体需要执行的操作变成了:
每次循环减少了2步操作,当循环的数量级很大的时候,性能提升会很显著。
一次数量级很大的循环的开销是比较大的,可以把一个大循环拆开成几个小循环,最常见的模式是“达夫设备(Duff’s Device.)”:把一个大的循环展开为几个小的循环,总的循环次数不变。
//Jeff Greenberg 在2/2001把Duff’s Device从C语言移植到Javascript中 var iterations = Math.floor(items.length / 8), startAt = items.length % 8, i = 0; do { switch(startAt){ case 0: process(items[i++]); case 7: process(items[i++]); case 6: process(items[i++]); case 5: process(items[i++]); case 4: process(items[i++]); case 3: process(items[i++]); case 2: process(items[i++]); case 1: process(items[i++]); } startAt = 0; } while (--iterations);
Duff’s Device把大循环分成每个迭代8次的小循环,8的余数再执行一次小循环。原理:在switch语句中,每个case不break的话,她后面的case都会被执行。这种方法在迭代次数超过1000以上时,效果比较明显。
假设一个800003次的循环,会被分成100001个小循环,第一个小循环迭代3次(余数为3,执行case 3、case 2、case 1),其余的100000次(总数除以8取整)每次迭代8次。
//Jeff Greenberg优化版 var i = items.length % 8; while(i){ process(items[i--]); } i = Math.floor(items.length / 8); while(i){ process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); }
Duff’s device的各种优化版本性能比较http://jsperf.com/duffs-device,^位运算符,浮点数与整数(整数优)、除法与乘法的性能比较(乘法优)。
foreach方法会先遍历成员,再在每个成员上执行一个函数,这是基于函数的迭代,因为要调用外部的函数方法,基于函数的迭代要慢(是循环迭代的1/8)。
items.forEach(function(value, index, array){ process(value); });
使用if-else还是switch?if-else:件数量很小时(2个或少数几个);件数量比较大时,switch(超过2个)。
优化if-else
优化目标:用最小的条件判断次数,达到正确的分支。
方法:
1.把出现几率最多条件放在首位,按出现几率的大小顺序往后。
if (value < 5) { //do something } else if (value > 5 && value < 10) { //do something } else { //do something }
这个if-else,只有在value的值大部分情况下都小于5的时候,只需要一个条件判断,性能才是最优的。如果value的值为8的话,需要2次条件判断,消耗时间会增加。
2.if-else嵌套。
if (value < 6){ if (value < 3){ if (value == 0){ return result0; } else if (value == 1){ return result1; } else { return result2; } } else { if (value == 3){ return result3; } else if (value == 4){ return result4; } else { return result5; } } } else { if (value < 8){ if (value == 6){ return result6; } else { return result7; } } else { if (value == 8){ return result8; } else if (value == 9){ return result9; } else { return result10; } } }
嵌套之后的语句,最多经过4次条件判断,就可以找到正确的分支,当value的值平均分布的时候,这样的嵌套可以节约大约50%的时间。
当有大量的离散值需要判读的时候,最好的方法避免使用if-else和switch,查找表(Lookup Tables)更快,Javascript中的查找表通过数组或对象的成员查询来实现,完全抛弃了条件判断。查找表的消耗和成员的多少没有关系,数量增大时几乎不会产生额外的开销;尤其是当条件判断中的value和对象成员之间存在对应关系时,查找表的优势最为突出:下例中的 results[value],不用进行任何判读,直接获取results的value成员。
//数组 var results = [result0, result1, result2, result3...] //查找成员查找 return results[value]; //对象 var obj = { num1:"value1", num2:"value2", num3:"value3", ... } //对象成员查找 return obj[value];
最常见的递归就是阶乘了:
function factorial(n){ if (n == 0){ //终止条件 return 1; } else { return n * factorial(n-1); } }
递归中最关键的是终止条件,还有浏览器的调用栈限制(Call Stack Limits),也就是说浏览器有最大的递归次数限制。因此递归的出问题的时候可以从终止条件和递归次数限制两个方面来分析和改善。
解决调用栈限制的方法,可以用循环迭代来替代递归,而且循环比迭代中反复运行一个函数开销要小,虽然有时候循环要比迭代慢一些,但这样可以避开调用栈限制。
function merge(left, right){ var result = []; while (left.length > 0 && right.length > 0){ if (left[0] < right[0]){ result.push(left.shift()); } else { result.push(right.shift()); } } return result.concat(left).concat(right); } //归并排列的递归实现 function mergeSort(items){ if (items.length == 1) { return items; } var middle = Math.floor(items.length / 2), left = items.slice(0, middle), right = items.slice(middle); return merge(mergeSort(left), mergeSort(right)); } //归并排列的循环实现 function mergeSort(items){ if (items.length == 1) { return items; } var work = []; for (var i=0, len=items.length; i < len; i++){ work.push([items[i]]); } work.push([]); //in case of odd number of items for (var lim=len; lim > 1; lim = (lim+1)/2){ for (var j=0,k=0; k < lim; j++, k+=2){ work[j] = merge(work[k], work[k+1]); } work[j] = []; //in case of odd number of items } return work[0]; }
Memoization:利用一个缓存对象,缓存前一次的计算结果共后续计算使用,避免重复计算工作,提高速度。
function memfactorial(n){ if (!memfactorial.cache){ memfactorial.cache = { "0": 1, "1": 1 }; } if (!memfactorial.cache.hasOwnProperty(n)){ memfactorial.cache[n] = n * memfactorial (n-1); } return memfactorial.cache[n]; } var fact6 = memfactorial(6); //Memoization 封装 function memoize(fundamental, cache){ cache = cache || {}; var shell = function(arg){ if (!cache.hasOwnProperty(arg)){ cache[arg] = fundamental(arg); } return cache[arg]; }; return shell; } //阶乘 function factorial(n){ if (n == 0){ return 1; } else { return n * factorial(n-1); } } //Memoization 阶乘 var memfactorial = memoize(factorial, { "0": 1, "1": 1 }); var fact6 = memfactorial(6);
tips:
var a = ["one","two"]; a.name= "name"; for (p in a) { alert(a[p]); }
此外Jeff Greenberg也有一篇关于Javascript优化的文章,推荐一下。http://home.earthlink.net/~kendrasg/info/js_opt/jsOptMain.html