`

转 高性能JavaScript 循环语句和流程控制

 
阅读更多

众所周知,常用的循环语句有for、while、do-while以及for-in,forEach。除了for-in和forEach性能略低 外,平时我们对前三者的选择更多的是基于需求而非性能考虑,今天我们就对它们各自的性能做个测试,告诉我们最极端的情况下还能做哪些优化。

  首先我们来谈谈为何for-in和forEach会比其他的慢。for-in一般是用在对象属性名的遍历上的,由于每次迭代操作会同时搜索实例 本身的属性以及原型链上的属性,所以效率肯定低下;而forEach是基于函数的迭代(需要特别注意的是所有版本的ie都不支持,如果需要可以用 JQuery等库),对每个数组项调用外部方法所带来的开销是速度慢的主要原因。

  接着我们看看每次迭代中for、while以及do-while都做了什么。

复制代码
var length = items.length;
for(var i = 0; i < length; i++)
  process(items[i]);

var j = 0;
while(j < length) 
  process(items[j++]);

var k = 0;
do {
  process(items[k++]);
} while(k < length);
复制代码

  上面的每个循环中,每次运行循环体时都会产生这样的操作:

  1. 一次控制条件中的数值大小比较(i < length)
  2. 一次控制条件结果是否为true的比较(i < length === true)
  3. 一次自增操作(i++)
  4. 一次数组查找(items[i])
  5. 一次函数调用process(items[i])

  我们可以通过颠倒数组的顺序来提高循环性能:

复制代码
for(var i = items.length; i--; )
  process(items[i]);

var j = items.length;
while(j--) 
  process(items[j]);

var k = items.length - 1;
do {
  process(items[k]);
} while(k--);
复制代码

  本例中使用了倒序循环,并把减法操作整合在循环条件中。现在每个控制条件只是简单地与0比较。控制条件与true值比较,任何非零数会自动转换 为true,而零值等同于false。实际上,控制条件从两个比较(迭代数少于总数吗?它是true吗?)减少到一次比较(它是true吗?)。每次迭代 从两次比较减少到一次,进一步提高了循环速度。

 

性能测试:

  那么事实真的如此吗?真金不怕浏览器验。测试代码很简单,针对不同的8种情况封装了8个函数(不加定时器firefox下无法打印profiles信息,原因不明):

复制代码
// init array
var a = [];
var length = 10;
for(var i = 0; i < length; i++)
  a[i] = 1;

function for_in() {
  var sum = 0;
  for(var i in a) 
    sum += a[i];
}

function for_each() {
  var sum = 0;
  a.forEach(function(value, index, array) {
    sum += value;
  });
}

function for_normal() {
  var sum = 0;
  for(var i = 0; i < length; i++)
    sum += a[i];
}

function for_reverse() {
  var sum = 0;
  for(var i = length; i--; )
    sum += a[i];
}

function while_normal() {
  var sum = 0;
  var i = 0;
  while(i < length) 
    sum += a[i++];
}

function while_reverse() {
  var sum = 0;
  var i = length;
  while(i--) 
    sum += a[i];
}

function do_while_normal() {
  var sum = 0;
  var i = 0;
  do {
    sum += a[i++];
  } while(i < length);
}

function do_while_reverse() {
  var sum = 0;
  var i = length - 1;
  do {
    sum += a[i];
  } while(i--);
}


setTimeout(function() {
  console.profile();
  for_in();
  for_each();
  for_normal();  
  for_reverse();
  while_normal();
  while_reverse();
  do_while_normal();
  do_while_reverse();
  console.profileEnd();
}, 1000);
复制代码

  当数组长度为100时,我们发现firefox下的结果确实和预料的相似:for-each和for-in效率低下,倒序比正序效率略微提升。(chrome下的profiles由于时间太短不显示)

  当数据量达到100w时,firefox和chrome下的结果都如人所愿,但是也略微有所不同。ff下的for-in表现地比for- each好,而chrome下for-in表现糟糕,直接提出了警告。而倒序迭代虽然性能略微有所提升,但是提升的不是很多,且降低了代码阅读性。

 

 小结:

  1. 倒序迭代确实能略微提升代码性能,但是牺牲了代码可读性,除非追求极端性能优化情况下不然没必要用
  2. 遍历数组能用普通的循环就不要用for-in和for-each

条件语句

  常见的条件语句有if-else和switch-case,那么什么时候用if-else,什么时候用switch-case语句呢?

  我们先来看个简单的if-else语句的代码:

复制代码
if (value == 0){
    return result0;
} else if (value == 1){
    return result1;
} else if (value == 2){
    return result2;
} else if (value == 3){
    return result3;
} else if (value == 4){
    return result4;
} else if (value == 5){
    return result5;
} else if (value == 6){
    return result6;
} else if (value == 7){
    return result7;
} else if (value == 8){
    return result8;
} else if (value == 9){
    return result9;
} else {
    return result10;
}
复制代码

  最坏的情况下(value=10)我们可能要做10次判断才能返回正确的结果,那么我们怎么优化这段代码呢?一个显而易见的优化策略是将最可能 的取值提前判断,比如value最可能等于5或者10,那么将这两条判断提前。但是通常情况下我们并不知道(最可能的选择),这时我们可以采取二叉树查找 策略进行性能优化。

复制代码
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值是连续的 数字时才能进行这样的优化。但是代码这样写的话不利于维护,如果要增加一个条件,或者多个条件,就要重写很多代码,这时switch-case语句就有了 用武之地。

  将以上代码用switch-case语句重写:

复制代码
switch(value){
    case 0:
        return result0;
    case 1:
        return result1;
    case 2:
        return result2;
    case 3:
        return result3;
    case 4:
        return result4;
    case 5:
        return result5;
    case 6:
        return result6;
    case 7:
        return result7;
    case 8:
        return result8;
    case 9:
        return result9;
    default:
        return result10;
}
复制代码

  swtich-case语句让代码显得可读性更强,而且swtich-case语句还有一个好处是如果多个value值返回同一个结果,就不用 重写return那部分的代码。一般来说,当case数达到一定数量时,swtich-case语句的效率是比if-else高的,因为switch- case采用了branch table(分支表)索引来进行优化,当然各浏览器的优化程度也不一样。

  除了if-else和swtich-case外,我们还可以采用查找表。

var results = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10];

//return the correct result
return results[value];

  当数据量很大的时候,查找表的效率通常要比if-else语句和swtich-case语句高,查找表能用数字和字符串作为索引,而如果是字符 串的情况下,最好用对象来代替数组。当然查找表的使用是有局限性的,每个case对应的结果只能是一个取值而不能是一系列的操作。

 

小结:

  1. 当只有两个case或者case的value取值是一段连续的数字的时候,我们可以选择if-else语句
  2. 当有3~10个case数并且case的value取值非线性的时候,我们可以选择switch-case语句
  3. 当case数达到10个以上并且每次的结果只是一个取值而不是额外的JavaScript语句的时候,我们可以选择查找表 
分享到:
评论

相关推荐

    通过循环优化 JavaScript 程序

    对于提高 JavaScript 程序的性能这个问题,最简单同时也是很容易被忽视的方法就是学习如何正确编写高性能循环语句。本文将会帮你解决这个问题。 我们将看到 JavaScript 中主要的循环类型,以及如何针对它们进行高效...

    精通JavaScript

    • 4.11.htm for循环语句之二 • 4.12.htm continue语句 第5章(\第5章) • 5.1.htm 函数定义 • 5.2.htm 无返回值的函数 • 5.3.htm 有返回值的函数及其调用 • 5.4....

    SQL查询安全性及性能优化

    SQL注入的原理 什么SQL注入 将SQL代码插入到应用程序的输入参数中,之后,SQL代码被传递到数据库执行。从而达到对应用程序的攻击目的。... 有了执行计划和执行时间我们就很容易判断一条SQL语句执行效率高不高

    php课程(共100多节)

    5:PHP循环语句的介绍与应用 6:PHP数组的创建修改应用 7:PHP函数和自定义函数 8:Mysql 简介和创建新的数据库 9:数据库中的常用SQL语句 10:MYSQL在PHP5中的应用 11:学习制作PHP+MYSQL留言板(上) 12:学习制作...

    PHP和MySQL Web开发第4版pdf以及源码

    3.3.3 使用循环语句 3.4 数组操作符 3.5 多维数组 3.6 数组排序 3.6.1 使用sort()函数 3.6.2 使用asort()函数和ksort()函数对相关数组排序 3.6.3 反向排序 3.7 多维数组的排序 3.7.1 用户定义排序 3.7.2 ...

    PHP和MySQL WEB开发(第4版)

    3.3.3 使用循环语句 3.4 数组操作符 3.5 多维数组 3.6 数组排序 3.6.1 使用sort()函数 3.6.2 使用asort()函数和ksort()函数对相关数组排序 3.6.3 反向排序 3.7 多维数组的排序 3.7.1 用户定义排序 3.7.2 反向用户...

    PHP和MySQL Web开发第4版

    3.3.3 使用循环语句 3.4 数组操作符 3.5 多维数组 3.6 数组排序 3.6.1 使用sort()函数 3.6.2 使用asort()函数和ksort()函数对相关数组排序 3.6.3 反向排序 3.7 多维数组的排序 3.7.1 用户定义排序 3.7.2 ...

    PHP知识目录最新版本

    以后越来越多的网站使用了PHP,并且强烈要求增加一些特性,比如循环语句和数组变量等等,在新的成员加入开发行列之后,在1995年中,PHP2.0发布了。第二版定名为PHP/FI(Form Interpreter)。PHP/FI加入了对mSQL的支持...

    JS高级程序设计-核心笔记.docx

    JavaScript高级程序设计(第3版) 自学笔记 1 章 JavaScript 简介 .................................1 1.1 JavaScript 简史 ..........................................1 1.2 JavaScript 实现 .....................

    PHP100视频教程全集112集BT种子【PHP经典】

    PHP100视频教程5:PHP循环语句的介绍与应用 PHP100视频教程6:PHP数组的创建修改应用 PHP100视频教程7:PHP函数和自定义函数 PHP100视频教程8:Mysql 简介和创建新的数据库 PHP100视频教程9:数据库中的常用SQL...

    最新Java面试宝典pdf版

    64、说出ArrayList,Vector, LinkedList的存储性能和特性 46 65、去掉一个Vector集合中重复的元素 46 66、Collection 和 Collections的区别。 47 67、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用...

    Java面试笔试资料大全

    64、说出ArrayList,Vector, LinkedList的存储性能和特性 46 65、去掉一个Vector集合中重复的元素 46 66、Collection 和 Collections的区别。 47 67、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用...

    Java面试宝典-经典

    64、说出ArrayList,Vector, LinkedList的存储性能和特性 46 65、去掉一个Vector集合中重复的元素 46 66、Collection 和 Collections的区别。 47 67、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用...

Global site tag (gtag.js) - Google Analytics