高性能JavaScript循环语句和条件语句


Posted in Javascript onJanuary 20, 2016

一、循环语句
众所周知,常用的循环语句有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);

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

  • 一次控制条件中的数值大小比较(i < length)
  • 一次控制条件结果是否为true的比较(i < length === true)
  • 一次自增操作(i++)
  • 一次数组查找(items[i])
  • 一次函数调用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表现糟糕,直接提出了警告。而倒序迭代虽然性能略微有所提升,但是提升的不是很多,且降低了代码阅读性。

高性能JavaScript循环语句和条件语句

高性能JavaScript循环语句和条件语句

小结:

  • 倒序迭代确实能略微提升代码性能,但是牺牲了代码可读性,除非追求极端性能优化情况下不然没必要用
  • 遍历数组能用普通的循环就不要用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对应的结果只能是一个取值而不能是一系列的操作。

小结:

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

以上就是本文的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
javascript 支持ie和firefox杰奇翻页函数
Jul 22 Javascript
jQuery的each终止或跳过示例代码
Dec 12 Javascript
JavaScript中的prototype和constructor简明总结
Apr 05 Javascript
javascript数组操作总结和属性、方法介绍
Apr 05 Javascript
JavaScript中字符串分割函数split用法实例
Apr 07 Javascript
理解Javascript的call、apply
Dec 16 Javascript
基于Bootstrap实现tab标签切换效果
Apr 15 Javascript
JS 根据子网掩码,网关计算出所有IP地址范围示例
Apr 23 Javascript
Angular使用ng-messages与PHP进行表单数据验证
Dec 28 Javascript
详解swiper在vue中的应用(以3.0为例)
Sep 20 Javascript
在博客园博文中添加自定义右键菜单的方法详解
Feb 05 Javascript
基于javascript canvas实现五子棋游戏
Jul 08 Javascript
详解Javascript模板引擎mustache.js
Jan 20 #Javascript
JavaScript优化专题之Loading and Execution加载和运行
Jan 20 #Javascript
JQuery日历插件My97DatePicker日期范围限制
Jan 20 #Javascript
在其他地方你学不到的jQuery小贴士和技巧(欢迎收藏)
Jan 20 #Javascript
js实现图片无缝滚动特效
Mar 19 #Javascript
学习JavaScript设计模式之迭代器模式
Jan 19 #Javascript
学习JavaScript设计模式之观察者模式
Apr 22 #Javascript
You might like
《心理测量者3》剧场版动画预告
2020/03/02 日漫
上海地方志办公室-上海电子仪表工业志
2021/03/04 无线电
PHP通用分页类page.php[仿google分页]
2008/08/31 PHP
php strcmp使用说明
2010/04/22 PHP
php redis实现文章发布系统(用户投票系统)
2017/03/04 PHP
CentOS7系统搭建LAMP及更新PHP版本操作详解
2020/03/26 PHP
htm调用JS代码
2007/03/15 Javascript
javascript代码加载优化方法
2011/01/30 Javascript
JS实现鼠标经过好友列表中的好友头像时显示资料卡的效果
2014/07/02 Javascript
Jquery设置attr的disabled属性控制某行显示或者隐藏
2014/09/25 Javascript
javascript数据类型示例分享
2015/01/19 Javascript
JavaScript中的lastIndexOf()方法使用详解
2015/06/06 Javascript
移动端手指放大缩小插件与js源码
2017/05/22 Javascript
深入理解AngularJs-scope的脏检查(一)
2017/06/19 Javascript
jQuery实现仿京东防抖动菜单效果示例
2018/07/06 jQuery
解决Vue 项目打包后favicon无法正常显示的问题
2018/09/01 Javascript
JavaScript&quot;模拟事件&quot;的注意要点详解
2019/02/13 Javascript
ES6小技巧之代替lodash
2019/06/07 Javascript
浅谈Vue项目骨架屏注入实践
2019/08/05 Javascript
Vue开发环境中修改端口号的实现方法
2019/08/15 Javascript
layui form表单提交后实现自动刷新
2019/10/25 Javascript
vue+flask实现视频合成功能(拖拽上传)
2021/03/04 Vue.js
Python使用metaclass实现Singleton模式的方法
2015/05/05 Python
jupyter notebook引用from pyecharts.charts import Bar运行报错
2020/04/23 Python
Python入门学习指南分享
2018/04/11 Python
元组列表字典(莫烦python基础)
2019/04/03 Python
Django如何简单快速实现PUT、DELETE方法
2019/07/24 Python
Python爬虫scrapy框架Cookie池(微博Cookie池)的使用
2021/01/13 Python
KEETSA环保床垫:更好的睡眠,更好的生活!
2016/11/24 全球购物
PHP使用Redis队列执行定时任务实例讲解
2021/03/24 PHP
公务员综合考察材料
2014/02/01 职场文书
怎样写离婚协议书
2014/09/10 职场文书
教师自查自纠工作情况报告
2014/10/29 职场文书
物流仓管员岗位职责
2015/04/01 职场文书
2015年学生资助工作总结
2015/05/25 职场文书
mysql 获取相邻数据项
2022/05/11 MySQL