高性能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 相关文章推荐
ArrayList类(增强版)
Apr 04 Javascript
(跨浏览器基础事件/浏览器检测/判断浏览器)经验代码分享
Jan 24 Javascript
利用js实现遮罩以及弹出可移动登录窗口
Jul 08 Javascript
浅谈jquery之on()绑定事件和off()解除绑定事件
Oct 26 Javascript
node.js实现微信JS-API封装接口的示例代码
Sep 06 Javascript
javascript计算对象长度的方法
Oct 25 Javascript
JS获取url参数,JS发送json格式的POST请求方法
Mar 29 Javascript
详解webpack4之splitchunksPlugin代码包分拆
Dec 04 Javascript
微信小程序设置全局请求URL及封装wx.request请求操作示例
Apr 02 Javascript
微信小程序获取用户绑定手机号方法示例
Jul 21 Javascript
layui框架与SSM前后台交互的方法
Sep 12 Javascript
Vue 组件的挂载与父子组件的传值实例
Sep 02 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
ThinkPHP采用GET方式获取中文参数查询无结果的解决方法
2014/06/26 PHP
php中数字、字符与对象判断函数用法实例
2014/11/26 PHP
php redis setnx分布式锁简单原理解析
2020/10/23 PHP
js获取url参数的使用扩展实例
2007/12/29 Javascript
HTML 自动伸缩的表格Table js实现
2009/04/01 Javascript
原生javascript实现获取指定元素下所有后代元素的方法
2014/10/28 Javascript
JavaScript中的对象与JSON
2015/07/03 Javascript
jQuery实现获取绑定自定义事件元素的方法
2015/12/02 Javascript
jQuery包裹节点用法完整示例
2016/09/13 Javascript
在vue中添加Echarts图表的基本使用教程
2017/11/22 Javascript
JavaScript中关于class的调用方法
2017/11/28 Javascript
Javascript实现购物车功能的详细代码
2018/05/08 Javascript
JS实现常见的查找、排序、去重算法示例
2018/05/21 Javascript
jQuery插件实现的日历功能示例【附源码下载】
2018/09/07 jQuery
详解vue.js移动端配置flexible.js及注意事项
2019/04/10 Javascript
Angular CLI 使用教程指南参考小结
2019/04/10 Javascript
微信小程序实现卡片左右滑动效果的示例代码
2019/05/01 Javascript
详解ES6 Promise的生命周期和创建
2019/08/18 Javascript
使用js原生实现年份轮播选择效果实例
2021/01/12 Javascript
PHP网页抓取之抓取百度贴吧邮箱数据代码分享
2016/04/13 Python
浅谈Python 的枚举 Enum
2017/06/12 Python
在python2.7中用numpy.reshape 对图像进行切割的方法
2018/12/05 Python
详解Python用户登录接口的方法
2019/04/17 Python
python使用Qt界面以及逻辑实现方法
2019/07/10 Python
python如何变换环境
2020/07/21 Python
Pycharm的Available Packages为空的解决方法
2020/09/18 Python
TensorFlow的环境配置与安装方法
2021/02/20 Python
CHARLES & KEITH台湾官网:新加坡时尚品牌
2019/07/30 全球购物
管理信息系学生的自我评价
2014/01/11 职场文书
六年级数学教学反思
2014/02/03 职场文书
学生操行评语大全
2014/04/24 职场文书
村支部书记群众路线对照检查材料思想汇报
2014/10/08 职场文书
新郎婚礼答谢词
2015/01/04 职场文书
小学少先队辅导员述职报告
2015/01/10 职场文书
入党转正申请报告
2015/05/15 职场文书
《雪域豹影》读后感:父爱的伟大
2019/12/23 职场文书