如何缩短我的条件语句

155

我有一个非常长的条件语句,类似于以下内容:

if(test.type == 'itema' || test.type == 'itemb' || test.type == 'itemc' || test.type == 'itemd'){
    // do something.
}

我在想是否能将这个表达式/语句重构为更简洁的形式。

你有什么想法吗?


23
你可以将它们放入一个数组中,并使用in吗? - jeremy
2
http://snook.ca/archives/javascript/testing_for_a_v - Muhammad Umer
现在只要有人能够检查哪一个是最快的。 - Muhammad Umer
3
也许会让大家震惊,但是 OP 所拥有的速度绝对是完胜的!!!!!!! 可能是因为浏览器对此进行了很多优化..结果: (1) 如果用 ||. (2) switch 语句. (3) 正则表达式. (4) ~. http://jsperf.com/if-statements-test-techsin - Muhammad Umer
4
你可能正在错误的方向上接近这个问题。在这种情况下,这4种类型有共同点。如果我们将其扩展到更极端的情况,假设我们需要添加10种或100种类型以匹配此条件,那么你可能不会考虑使用此解决方案或其他建议。你看到像这样的一个大if语句,认为它是代码异味,这是一个好迹象。让代码更简洁的最佳方式是,如果你可以编写 if (test.your_common_condition)。在这种情况下,更容易理解,并且更具可扩展性。 - gmacdougall
显示剩余5条评论
15个回答

242
将您的值放入数组中,并检查您的项是否在数组中:
if ([1, 2, 3, 4].includes(test.type)) {
    // Do something
}

如果您支持的浏览器没有Array#includes方法,您可以使用此填充程序

~波浪线快捷方式的简短解释:

更新:由于我们现在有了includes方法,所以没有必要再使用~hack。只是为那些对它如何工作感兴趣和/或在别人的代码中遇到它的人保留这个。

与其检查indexOf的结果是否>= 0,有一个很好的小技巧:

if ( ~[1, 2, 3, 4].indexOf(test.type) ) {
    // Do something
}

这里有一个 JSFiddle: http://jsfiddle.net/HYJvK/

它是如何工作的?如果在数组中找到了一个元素,indexOf会返回它的索引。如果没有找到该元素,则返回-1。不过,不用深入细节,~是一个按位取反运算符,只有在-1时才会返回0

我喜欢使用~快捷方式,因为它比对返回值进行比较更加简洁。我希望 JavaScript 有一个直接返回布尔值的in_array函数(类似于 PHP),但这只是一厢情愿(更新:现在有了。它被称为includes。请参见上文)。请注意,jQuery的inArray虽然与PHP的方法签名相同,但实际上模仿了本地的indexOf功能(如果索引是您真正想要的,则在不同情况下非常有用)。

重要提示:使用波浪线快捷方式似乎充满争议,因为一些人激烈地认为代码不够清晰,应该尽量避免使用(请参见此答案的评论)。如果您分享他们的观点,您应该坚持使用.indexOf(...) >= 0解决方案。


稍微详细的解释:

JavaScript中的整数是有符号的,这意味着最左边的位被保留为符号位;一个标志,用于指示数字是正数还是负数,其中1表示负数。

以下是一些32位二进制格式的正数示例:

1 :    00000000000000000000000000000001
2 :    00000000000000000000000000000010
3 :    00000000000000000000000000000011
15:    00000000000000000000000000001111

现在这些数字是负数:
-1 :   11111111111111111111111111111111
-2 :   11111111111111111111111111111110
-3 :   11111111111111111111111111111101
-15:   11111111111111111111111111110001

为什么负数要这样奇怪地组合?很简单。负数就是正数的相反数加1;将负数与正数相加总应该得到0。
为了理解这一点,让我们进行一些简单的二进制算术运算。
这里是如何将-1加到+1:
   00000000000000000000000000000001      +1
+  11111111111111111111111111111111      -1
-------------------------------------------
=  00000000000000000000000000000000       0

以下是如何将-15加到+15的方法:

   00000000000000000000000000001111      +15
+  11111111111111111111111111110001      -15
--------------------------------------------
=  00000000000000000000000000000000        0

我们如何得到这些结果?通过常规的加法,就像我们在学校里学的那样:从最右边的列开始,将所有行相加。如果总和大于最大的一位数(十进制中为9,但在二进制中为1),则将余数向下一列进位。
现在,您会注意到,当将负数加上它的正数时,不全为0的最右列始终会有两个1,将它们相加将得到2。二的二进制表示为10,我们将1进位到下一列,并在第一列的结果中放置0。左侧的所有其他列只有一个带有1的行,因此从前一列传递过来的1将再次相加得到2,然后再次进位......这个过程重复进行,直到我们到达最左列,在那里要被传递的1无处可去,所以它溢出并且消失了,我们留下横跨全列的0
这个系统被称为二进制补码。您可以在此处阅读更多相关内容:有符号整数的二进制补码表示法
现在2的补码课程已经结束,你会注意到-1是唯一一个二进制表示中全是1的数字。
使用位取反运算符~,给定数字中的所有位都会被反转。从反转所有位得到0的唯一方法是如果我们最初就是全是1。
因此,这一切都是说~n只有在n等于-1时才会返回0。

59
使用位运算符虽然很酷,但在任何情况下它真的比!== -1更好吗?明确的布尔逻辑难道不是比隐式地使用零的假值更合适吗? - Phil
21
技术性很强,但我不喜欢它。一开始看不清代码在做什么,这使得它难以维护。我更喜欢"Yuriy Galanter"的回答。 - Jon Rea
65
新手程序员看到像这样的答案,认为这是一种很酷、可接受的编程方式,然后在五年后我不得不维护他们的代码并且让我抓狂。 - BlueRaja - Danny Pflughoeft
23
这个成语在像C#、Java或Python这样的语言中绝对不常见,而这些是我擅长的领域。我刚刚问了一下这里几位本地的Javascript专家,他们都从没见过它被使用过,因此它显然没有你所说的那么普遍。应该始终避免使用它,而改用更清晰、更常见的"!= -1"。 - BlueRaja - Danny Pflughoeft
12
由于在一种并没有很多关于位表示的具体规定的语言中进行了不必要的位操作,导致得分为-1。此外,这个回答的大部分内容都是在解释这个位操作。如果你需要写20段来解释这个技巧,那么它真的能省下时间吗? - fluffy
显示剩余32条评论

242

你可以使用带有“fall thru”的switch语句:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}

9
基本上和if语句差不多,不过使用数组索引的方法要好得多。 - NimChimpsky
6
很遗憾,正确答案是这个,但是很难引起人们对此的关注,而不是那个令人惊艳的位数组技巧。 - Manu343726
1
我知道这个答案一定在这里,但我不得不向下滚动到底部才找到它。这正是switch语句的设计初衷,并且适用于许多其他编程语言。我发现很多人不知道switch语句中的“fall through”方法。 - Jimmy Johnson
3
这个解决方案在 Firefox 和 Safari 上是最快的,在 Chrome 上是第二快的(仅次于原始的 ||)。请参见 http://jsperf.com/if-statements-test-techsin。 - pabouk - Ukraine stay strong
3
如果有很多条件需要写入方法中,我认为这会很麻烦。如果只有几个条件的话还好,但是如果超过10个,最好使用数组来保持代码整洁。请注意,我的翻译尽可能保留原文含义和结构,同时使其更易于理解。 - yu_ominae
显示剩余6条评论

63

运用科学:你应该按照idfah所说的方法并且使用以下这个方法,以获得最快的速度同时保持代码简洁:

这种方法比~方法更快

var x = test.type;
if (x == 'itema' ||
    x == 'itemb' ||
    x == 'itemc' ||
    x == 'itemd') {
    //do something
}

http://jsperf.com/if-statements-test-techsin enter image description here (Top set: Chrome, bottom set: Firefox)

结论:

如果可能性很少,并且你知道某些可能性比其他更容易发生,那么使用if ||switch fall throughif(obj[keyval])可以获得最大的性能。

如果可能性很多,并且任何一个可能性都可能是最常发生的,换句话说,你不知道哪一个最有可能发生,那么使用对象查找if(obj[keyval])regex可以获得最佳性能。

http://jsperf.com/if-statements-test-techsin/12

如果有新内容出现,我会进行更新。


2
非常好的帖子!如果我理解正确,switch case是最快的方法? - user1477388
1
在 Firefox 中是可以的,在 Chrome 中应该写成 if ( ...||...||...)... - Muhammad Umer
8
如果你对输入进行多次循环,速度会更快,但是如果你只有一个循环,n值(“itemX”字符串的数量)非常大,则速度会慢得多。我编写了这个代码生成器,你可以使用它来验证(或者也许是反驳)这一点。当n很大时,obj["itemX"] 的速度非常快。基本上,速度快慢取决于上下文。祝你玩得开心。 - kojiro
3
这是最快的方法,但这有关紧要吗 - congusbongus
1
@Mich 不要牺牲代码的优雅性只为了追求速度。这是许多人会对你说的话。最终,只需运用常识即可。 - Andre Figueiredo
显示剩余6条评论

32

如果您正在比较字符串并有一个模式,请考虑使用正则表达式。

否则,我怀疑试图缩短它只会使您的代码更加晦涩难懂。考虑简单地换行来使它看起来更美观。

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}

4
这个答案在速度方面是最优胜者。 http://jsperf.com/if-statements-test-techsin - Muhammad Umer
1
这也是在项目进入维护模式时最容易扩展的(例如,使用以下规则:(test.type == 'itemf' && foo.mode == 'detailed'))。 - Izkata

16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) { … }

在JavaScript中,使用对象作为关联数组是一种很常见的方式。但由于JavaScript没有原生的集合数据结构,因此你也可以将对象用作简单的集合。


这种方式怎么比FlyingCat试图缩短的普通if语句还要更短呢? - dcarson
1
@dcarson OP的if语句条件如果删除所有空白字符,则需要78个字符。如果像这样编写:test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1},则我的条件只需要54个字符。从根本上讲,他的条件每增加两个键就需要四个字符,而我的条件只需要两个字符。 - kojiro
1
但是你可以这样写:if(possibilities[test.type]),就可以节省2个字符! :) - dc5

15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

或者,如果物品不是那么统一,那么:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }

9
有些人在面对问题时,会想“我知道,我会使用正则表达式。”现在他们有了两个问题。 - Jamie Zawinski, 1997 - Moshe Katz
5
虽然我能理解人们喜欢引用这句话,而且人们确实会将正则表达式用于完全不适当的事情,但这并不是其中之一。在OP提供的案例中,它不仅符合标准,而且做得非常好。正则表达式本质上并不邪恶,它的作用是匹配具有明确定义参数的字符串。 - Vala
3
通常情况下,我会认为提问者并不打算使用第一个人造示例进行匹配,并且真实的匹配情况并不那么简单。在这种情况下,我认为正则表达式并不是正确的解决方法。换句话说,如果您的正则表达式只是一个由管道符号分隔的选项列表,那么它并没有比其他建议更易读,可能效率显著降低。 - Moshe Katz

10

回答很好,但是您可以通过将其中一个包装在一个函数中使代码更易读。

这是一条复杂的if语句,当您(或其他人)在一年后阅读代码时,您将浏览以找到部分来理解正在发生的事情。具有此业务逻辑水平的语句将导致您在工作时绊倒几秒钟,而像这样的代码将使您能够继续扫描。

if(CheckIfBusinessRuleIsTrue())
{
    //Do Something
}

function CheckIfBusinessRuleIsTrue() 
{
    return (the best solution from previous posts here);
}

明确命名函数,使其立即显而易见你正在测试什么,这样你的代码将更容易扫描和理解。


1
这是我在这里看到的最佳答案。实际上,我发现人们并不关心良好设计的原则。他们只想快速修复问题,却忘记改进代码以便未来系统可维护! - Maykonn
那就像这样注释:// 检查业务规则是否为真,怎么样? - daniel1426

4
你可以将所有答案放入一个Javascript Set中,然后只需在集合上调用.contains()即可。
你仍然需要声明所有内容,但内联调用会更短。类似这样:
var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}

4
这似乎是一种极其浪费的方式来完成OP试图做的事情。因此,虽然您可以包括一个额外的第三方库,实例化一个对象并在其上调用一个方法,但您可能不应该这样做。 - KaptajnKold
@Captain Cold:嗯,OP要求简洁而不是内存占用。也许这个集合可以被重复使用于其他操作? - Guido Anselmi
1
当然,即使如此:你真的会这样做吗?如果我在现实中看到这种情况,我会认为这是一个非常让人费解的事情。 - KaptajnKold
1
是的,你说得对(我给了你+1),但这假设检查没有在其他任何地方进行。如果它在几个其他地方进行并/或测试发生变化,则使用Set可能是有意义的。我让OP选择最佳解决方案。话虽如此,如果这是孤立的用法,我同意使用Set会值得A-s小丑帽子的耻辱。 - Guido Anselmi
2
我认为所选答案非常糟糕,比使用Set更糟糕,因为它完全无法阅读。 - Guido Anselmi

2

使用switch语句代替if语句:

switch (test.type) {

  case "itema":case "itemb":case "itemc":case "itemd":
    // do your process
  case "other cases":...:
    // do other processes
  default:
    // do processes when test.type does not meet your predictions.
}

Switch 语句比在 if 中使用大量条件语句更快。


2
为了提高可读性,创建一个用于测试的函数(是的,只需要一行代码):
function isTypeDefined(test) {
    return test.type == 'itema' ||
           test.type == 'itemb' ||
           test.type == 'itemc' ||
           test.type == 'itemd';
}

然后调用它:
if (isTypeDefined(test)) {
…
}
...

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