使用Switch语句进行大小比较

344

我想像这样使用switch语句:

switch (scrollLeft) {
  case (<1000):
   //do stuff
   break;
  case (>1000 && <2000):
   //do stuff
   break;
}
现在我知道那两种语句(<1000)或者(>1000 && <2000)都行不通(显然是为了不同的原因)。我想问的是最有效的方法是什么。我不喜欢使用30个if语句,所以我宁愿使用switch语法。我能做些什么吗?

6
你的步骤是规律的吗? 我的意思是,如果你将scrollLeft除以1000,你就可以切换1、2、3... - IcanDivideBy0
或许你可以创建一个排序数组,将条件范围与相应的操作进行映射,并在其上应用二分查找。如果您的条件足够规则化,则可以直接调用 your_mapper_object[scrollLeft / SOME_CONST],假设 your_mapper_object 类似于 {1: some_func, 2: another_func, ...}。而且在这种情况下,也可以使用 switch。 - Overmind Jiang
2011年3月28日的重复问题 - Mr. Polywhirl
10个回答

1056
从其他答案中看到的解决方案,我注意到了一些我根据过去的经验知道对性能不利的事情。
我在不同的浏览器中测试了几种解决方案,并且以下是我的结果(可能因人而异),每个浏览器中最快操作的结果进行了归一化。
以下是2021年5月5日的结果。
测试 Chrome Firefox Opera Edge Brave Node
1.0 时间 15 毫秒 14 毫秒 17 毫秒 17 毫秒 16 毫秒 14 毫秒
if-immediate 1.00 1.00 1.00 1.00 1.00 1.00
if-indirect 2.20 1.21 2.06 2.18 2.19 1.93
switch-immediate 2.07 1.43 1.71 1.71 2.19 1.93
switch-range 3.60 2.00 2.47 2.65 2.88 2.86
switch-range2 2.07 1.36 1.82 1.71 1.94 1.79
switch-indirect-array 2.93 1.57 2.53 2.47 2.75 2.50
array-linear-switch 2.73 3.29 2.12 2.12 2.38 2.50
array-binary-switch 5.80 6.07 5.24 5.24 5.44 5.37
2021年的测试是在Windows 10 64位操作系统上进行的,使用以下版本的浏览器:Chrome 90.0.4430.212Firefox 89.0b13Opera 76.0.4017.123Edge 90.0.818.62Brave 1.24.85Node 16.1.0(在WSL下运行)。
由于苹果不再更新Windows版的Safari浏览器,所以仍然是5.1.7版本。在这次测试中,我将其替换为Brave浏览器。
以下是2012年9月4日的测试结果,供进行历史比较:
测试 Chrome Firefox Opera MSIE Safari Node
1.0 时间 37 毫秒 73 毫秒 68 毫秒 184 毫秒 73 毫秒 21 毫秒
if-immediate 1.0 1.0 1.0 2.6 1.0 1.0
if-indirect 1.2 1.8 3.3 3.8 2.6 1.0
switch-immediate 2.0 1.1 2.0 1.0 2.8 1.3
switch-range 38.1 10.6 2.6 7.3 20.9 10.4
switch-range2 31.9 8.3 2.0 4.5 9.5 6.9
switch-indirect-array 35.2 9.6 4.2 5.5 10.7 8.6
array-linear-switch 3.6 4.1 4.5 10.0 4.7 2.7
array-binary-switch 7.8 6.7 9.5 16.0 15.0 4.9
2012年的测试是在Windows 7 32位操作系统上进行的,使用以下版本:Chrome 21.0.1180.89mFirefox 15.0Opera 12.02MSIE 9.0.8112Safari 5.1.7。由于Windows上的Node计时器分辨率为10毫秒而不是1毫秒,因此Node在Linux 64位系统上运行。

if-immediate

在所有测试环境中,这是最快的方法,除了... 鼓声响起 MSIE!(惊讶吧,意外吧)。

这是推荐的实现方式。

if (val < 1000) { /*do something */ } else
if (val < 2000) { /*do something */ } else
...
if (val < 30000) { /*do something */ } else

if-indirect

这是一个变种的switch-indirect-array,但是使用if语句代替,并且在所有测试引擎中速度更快。

2021年比最快的测试慢了20-120%(2012年:0-280%)。Chrome在2021年(2.20)比2012年(1.2)花费更长的时间。

values=[
   1000,  2000, ... 30000
];
if (val < values[0]) { /* do something */ } else
if (val < values[1]) { /* do something */ } else
...
if (val < values[29]) { /* do something */ } else

switch-immediate

当你可以进行计算以获得索引时,这个方法适用。

在2021年,它比if-immediate慢40-120%(2012年:0-180%),除了在MSIE中,它实际上是最快的。

switch (Math.floor(val/1000)) {
  case 0: /* do something */ break;
  case 1: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

switch-range

它很慢,因为引擎需要为每个情况比较两次值。

2021年,它比最快的测试慢了1-2.6倍(2012年:1.6-38倍)。 Chrome从38倍提高到3.6倍,但仍然是测试中最慢的引擎。

switch (true) {
  case (0 <= val &&  val < 1000): /* do something */ break;
  case (1000 <= val &&  val < 2000): /* do something */ break;
  ...
  case (29000 <= val &&  val < 30000): /* do something */ break;
}

switch-range2

这是switch-range的一个变体,但每个case只有一个比较,因此速度更快。 case语句的顺序很重要,因为引擎会按照源代码的顺序测试每个case ECMAScript 2020 13.12.9

在2021年,它比最快的测试慢36-107%,但在2012年,它慢1-31倍。在这个测试中,Chrome仍然是性能最差的,但已经从32倍提高到2倍。

switch (true) {
  case (val < 1000): /* do something */ break;
  case (val < 2000): /* do something */ break;
  ...
  case (val < 30000): /* do something */ break;
}

switch-indirect-array

在这个变体中,范围被存储在一个数组中。

2021年,它比最快的测试慢了57-193%(2012年:3-35倍)。 所有测试过的引擎的性能都有所提升,尽管Chrome仍然是最慢的,但它的性能已经从35提高到2.93。

values=[1000,  2000 ... 29000, 30000];

switch(true) {
  case (val < values[0]): /* do something */ break;
  case (val < values[1]): /* do something */ break;
  ...
  case (val < values[29]): /* do something */ break;
}

数组线性搜索

在这个变种中,范围被存储在一个数组中。

2021年,它比最快的测试慢了57-193%(2012年:3-35倍)。 所有测试引擎的性能都有所提升,尽管Chrome仍然是最慢的,但已经从35提高到2.93。

values=[1000,  2000 ... 29000, 30000];

for (sidx=0, slen=values.length; sidx < slen; ++sidx) {
  if (val < values[sidx]) break;
}

switch (sidx) {
  case 0: /* do something */ break;
  case 1: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

数组二进制开关

这是数组线性开关的一个变种,但使用了二进制搜索。 不幸的是,它比线性搜索要慢。我不知道是我的实现有问题还是线性搜索更加优化。也有可能是键空间太小。

在2021年,这个方法比之前慢了4-5倍(2012年:4-16倍)。请勿使用

values=[0, 1000,  2000 ... 29000, 30000];

while(range) {
  range = Math.floor( (smax - smin) / 2 );
  sidx = smin + range;
  if ( val < values[sidx] ) { smax = sidx; } else { smin = sidx; }
}

switch (sidx) {
  case 0: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

结论

如果性能很重要,使用if语句或switch语句,使用立即值。


212
很少见到如此详细、结构紧凑的回答。大力点赞! - Rick Donohoe
24
感谢您对该问题的性能方面解释的大力支持! - Zoltán Schmidt
29
这就是为什么stackoverflow是最好的答案之一的原因。这个回答“永恒”,太棒了,感谢jsfiddle! - Jessy
3
获取信息和解释 - JayKandari
9
我真希望我能点个赞,这样的回答太详细了! - Kaspar Lee
显示剩余8条评论

124

另一种选择:

var scrollleft = 1000;
switch (true)
{
    case (scrollleft > 1000):
      alert('gt');
      break;
    case (scrollleft <= 1000):
      alert('lt');
      break; 
}

演示:http://jsfiddle.net/UWYzr/


4
这是一个更有价值的解决方案。+1 - IcanDivideBy0
3
这不就和 if(...) else if(...) 一样吗?虽然避免了 if,但我觉得并不是很优雅的替代方式。 - pimvdb
12
虽然编码优雅,但它会影响性能。在Chrome浏览器中,使用它比使用“if”语句慢近30倍。请参见我的这个答案 - some
1
然而,当处理的数据不大且可能仅是应用函数(例如验证单个用户输入)时,这种性能惩罚是可以忽略不计的,因此在这种情况下选择可读性而非性能。 - Jesús Franco
1
这正是我想要的。谢谢! - Ami Schreiber
显示剩余3条评论

23
switch (Math.floor(scrollLeft/1000)) {
  case 0: // (<1000)
   //do stuff
   break;
  case 1: // (>=1000 && <2000)
   //do stuff;
   break;
}

只有在步长规则是固定的情况下才有效...

编辑:由于这个解决方案不断获得赞,我必须提醒mofolo的解决方案更好。


1
顺便说一下,我使用了Math.round(scrollLeft/1000) - switz
@Switz - 请记住,999 < 1000 属于第0种情况,但 Math.round(999/1000) 属于第1种情况。另外,上面有一个错别字,在第1种情况中是 >= 1000,而不仅仅是 >1000。 - Igor
Mofolo的解决方案唯一的问题是在Chrome浏览器中比IcanDivideBy0的解决方案慢了约30倍。请参见下面的答案 - some

6
您可以使用与条件对应的功能创建自定义对象。
var rules = [{ lowerLimit: 0,    upperLimit: 1000, action: function1 },              { lowerLimit: 1000, upperLimit: 2000, action: function2 },              { lowerLimit: 2000, upperLimit: 3000, action: function3 }];

针对这些情况,定义相应的函数(如定义function1、function2等)

并"评估"规则

function applyRules(scrollLeft)
{
   for(var i=0; i>rules.length; i++)
   {
       var oneRule = rules[i];
       if(scrollLeft > oneRule.lowerLimit && scrollLeft < oneRule.upperLimit)
       {
          oneRule.action();
       }
   }
}

注意

我讨厌使用30个if语句。

很多时候,使用if语句更易于阅读和维护。我建议只有当您有很多条件并且未来可能出现很多增长的情况下,才使用以上建议。

更新
正如@Brad在评论中指出的那样,如果条件是互斥的(每次只能为真),则检查上限就足够了:

if(scrollLeft < oneRule.upperLimit)

只要条件按升序定义(首先是最低的一个,比如0至1000,然后是1000至2000),就可以使用。

action=function1 -- 这不应该是冒号吗?;-) -- 你也可以重构它,只设置一个上限,因为通过排除法,你不能落在两个组之间--除非你的意图是这样做(允许多个操作)。 - Brad Christie
@Brad,不是我那个意思,你说得对,上限应该足够了。我会添加更新的... - Nivas
我觉得这个简洁而清晰 +1 - pimvdb

4

你在//做一些事情方面具体在做什么?

可能你可以像下面这样做:

(scrollLeft < 1000) ? //do stuff
: (scrollLeft > 1000 && scrollLeft < 2000) ? //do stuff
: (scrollLeft > 2000) ? //do stuff
: //etc. 

4
这是另一个选项:
     switch (true) {
         case (value > 100):
             //do stuff
             break;
         case (value <= 100)&&(value > 75):
             //do stuff
             break;
         case (value < 50):
            //do stuff
             break;
     }

3

没有经过测试,不确定这是否有效,但在使用switch语句之前,为了设置变量,可以进行一些if语句

var small, big;

if(scrollLeft < 1000){
    //add some token to the page
    //call it small
}


switch (//reference token/) {
  case (small):
   //do stuff
   break;
  case (big):
   //do stuff;
   break;
}

1

更新被接受的答案(目前无法评论)。截至2016年1月12日,在Chrome浏览器中使用演示jsfiddle,switch-immediate是最快的解决方案。

结果: 时间分辨率:1.33

   25ms "if-immediate" 150878146 
   29ms "if-indirect" 150878146
   24ms "switch-immediate" 150878146
   128ms "switch-range" 150878146
   45ms "switch-range2" 150878146
   47ms "switch-indirect-array" 150878146
   43ms "array-linear-switch" 150878146
   72ms "array-binary-switch" 150878146

完成

 1.04 (   25ms) if-immediate
 1.21 (   29ms) if-indirect
 1.00 (   24ms) switch-immediate
 5.33 (  128ms) switch-range
 1.88 (   45ms) switch-range2
 1.96 (   47ms) switch-indirect-array
 1.79 (   43ms) array-linear-switch
 3.00 (   72ms) array-binary-switch

这真的取决于 - 15ms "if-immediate" 15ms "if-indirect" 15ms "switch-immediate" 37ms "switch-range" 28ms "switch-range2" 35ms "switch-indirect-array" 29ms "array-linear-switch" 62ms "array-binary-switch" 已完成 1.00 ( 15ms) if-immediate 1.00 ( 15ms) if-indirect 1.00 ( 15ms) switch-immediate 2.47 ( 37ms) switch-range 1.87 ( 28ms) switch-range2 2.33 ( 35ms) switch-indirect-array 1.93 ( 29ms) array-linear-switch 4.13 ( 62ms) array-binary-switchchrome 版本 48.0.2564.109 (64位) mac os x 10.11.3 - RenaissanceProgrammer
ATM Safari 9.X 在 Mac OS X 和 Safari iOS 9.3 上,“if-immediate” 明显是最佳选择。 - RenaissanceProgrammer
1
1 毫秒的差异太微小了,不值得关注。每次测试运行时它的变化都比这更大。关键是:使用有意义的编码风格,不要试图进行微观优化。 - some

1
在我的情况下(对百分比进行颜色编码,无需考虑性能),我很快写了这个代码:
function findColor(progress) {
    const thresholds = [30, 60];
    const colors = ["#90B451", "#F9A92F", "#90B451"];

    return colors.find((col, index) => {
        return index >= thresholds.length || progress < thresholds[index];
    });
}

1

我讨厌使用30个if语句

最近我也遇到了同样的情况,这是我解决的方法:

之前的代码:

if(wind_speed >= 18) {
    scale = 5;
} else if(wind_speed >= 12) {
    scale = 4;
} else if(wind_speed >= 9) {
    scale = 3;
} else if(wind_speed >= 6) {
    scale = 2;
} else if(wind_speed >= 4) {
    scale = 1;
}

之后:
var scales = [[4, 1], [6, 2], [9, 3], [12, 4], [18, 5]];
scales.forEach(function(el){if(wind_speed > el[0]) scale = el[1]});

如果您设置为“1, 2, 3, 4, 5”,那么它甚至可以更加简单:
var scales = [4, 6, 9, 12, 18];
scales.forEach(function(el){if(wind_speed >= el) scale++});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接