154

我有一个很长的条件语句,如下所示:

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

我想知道是否可以将此表达式/语句重构为更简洁的形式。

关于如何实现这一目标的任何想法?

4

15 回答 15

242

将您的值放入数组中,并检查您的项目是否在数组中:

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

如果你支持的浏览器没有这个Array#includes方法,你可以使用这个 polyfill


~波浪号快捷方式的简短说明:

更新:既然我们现在有了这个includes方法,那么再使用~hack 就没有意义了。只为那些有兴趣了解它的工作原理和/或在其他代码中遇到过它的人保留它。

不是检查indexOfis的结果,而是>= 0有一个不错的小捷径:

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

这是小提琴:http: //jsfiddle.net/HYJvK/

这是如何运作的?如果在数组中找到项目,则indexOf返回其索引。如果未找到该项目,它将返回-1。无需过多介绍, the~是一个按位 NOT 运算符,它将0仅返回 for -1

我喜欢使用~快捷方式,因为它比比较返回值更简洁。我希望 JavaScript 有一个in_array直接返回布尔值的函数(类似于 PHP),但这只是一厢情愿的想法(更新:现在可以了。它被称为includes. 见上文)。请注意,jQueryinArray在共享 PHP 的方法签名的同时,实际上模仿了本机indexOf功能(如果索引是您真正追求的,这在不同情况下很有用)。

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


稍微长一点的解释:

JavaScript 中的整数是有符号的,也就是说最左边的位被保留为符号位;一个标志,指示数字是正数还是负数,a1为负数。

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

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

现在这里是相同的数字,但为负数:

-1 :   11111111111111111111111111111111
-2 :   11111111111111111111111111111110
-3 :   11111111111111111111111111111101
-15:   11111111111111111111111111110001

为什么这些负数的奇怪组合?简单的。负数只是正数 + 1 的倒数;将负数加到正数应该总是 yield 0

为了理解这一点,让我们做一些简单的二进制算术。

以下是我们将如何添加-1+1

   00000000000000000000000000000001      +1
+  11111111111111111111111111111111      -1
-------------------------------------------
=  00000000000000000000000000000000       0

以下是我们将如何添加-15+15

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

我们如何得到这些结果?通过定期加法,就像我们在学校所教的那样:从最右边的一列开始,然后将所有行加起来。如果总和大于最大的一位数(十进制为9,但二进制为1),我们将余数移至下一列。

现在,您会注意到,当将一个负数添加到它的正数时,不是所有0s 的最右边的列将始终有两个1s,当它们相加时将导致2。两个的二进制表示10,我们将1带到下一列,并将0结果 a 放在第一列。左边的所有其他列只有一行带有 a 1,因此1从前一列结转的值将再次加起来2,然后将结转...这个过程不断重复,直到我们到达最左边的列,其中被1结转的无处可去,因此溢出并丢失,我们只剩下0s。

这个系统被称为2's Complement。您可以在此处阅读有关此内容的更多信息:

2 的有符号整数的补码表示


现在 2 的补码速成课程已经结束,您会注意到这-1是唯一一个二进制表示为1' 的数字。

使用~按位非运算符,给定数字中的所有位都被反转。从反转所有位中恢复过来的唯一方法0是,如果我们从1' 开始。

所以,所有这一切都是一种冗长的说法,只有在is~n时才会返回。0n-1

于 2013-08-21T00:32:22.677 回答
242

您可以将 switch 语句与fall thru 一起使用:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}
于 2013-08-21T00:36:38.090 回答
63

使用科学:你应该按照 idfah 所说的去做,这样可以在保持代码简短的同时获得最快的速度:

~这比方法快

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

http://jsperf.com/if-statements-test-techsin 在此处输入图像描述 (顶部:Chrome,底部:Firefox)

结论 :

如果可能性很小,并且您知道某些可能性比您获得最大性能的可能性更大if ||switch fall throughif(obj[keyval])

如果可能性很多,并且其中任何一个都可能是最常发生的一个,换句话说,您无法知道哪一个最有可能发生,而不是您从对象查找中获得最大的性能if(obj[keyval])以及regex是否合适。

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

如果有新的东西出现,我会更新。

于 2013-08-21T16:09:19.447 回答
32

如果您要与字符串进行比较并且存在模式,请考虑使用正则表达式。

否则,我怀疑试图缩短它只会混淆你的代码。考虑简单地包装线条以使其美观。

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}
于 2013-08-21T01:00:41.647 回答
16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) { … }

将对象用作关联数组是很常见的事情,但由于 JavaScript 没有原生集合,您也可以将对象用作廉价集合。

于 2013-08-21T00:44:27.290 回答
15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

或者如果项目不是那么统一,那么:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
于 2013-08-21T01:08:02.617 回答
10

很好的答案,但是您可以通过将其中一个包装在函数中来使代码更具可读性。

这是一个复杂的 if 语句,当您(或其他人)在一年内阅读代码时,您将通过扫描找到该部分以了解正在发生的事情。具有这种业务逻辑级别的语句将导致您在确定要测试的内容时绊倒几秒钟。像这样的代码,将允许您继续扫描。

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

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

明确命名您的函数,以便立即清楚您正在测试的内容,并且您的代码将更容易扫描和理解。

于 2013-08-27T20:18:07.453 回答
4

您可以将所有答案放入Javascript Set中,然后调用.contains()该集合。

您仍然必须声明所有内容,但内联调用会更短。

就像是:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
于 2013-08-21T01:18:00.003 回答
2

我最喜欢的实现这一点的方法之一是使用诸如 underscore.js 之类的库...

var isItem = _.some(['itema','itemb','itemc','itemd'], function(item) {
    return test.type === item;
});

if(isItem) {
    // One of them was true
}

http://underscorejs.org/#some

于 2013-08-21T00:49:34.643 回答
2

我发现的另一种方式或另一种很棒的方式是......

if ('a' in oc(['a','b','c'])) { //dosomething }

function oc(a)
{
  var o = {};
  for(var i=0;i<a.length;i++)  o[a[i]]='';
  return o;
}

当然,正如您所看到的,这使事情更进一步,并使它们易于遵循逻辑。

http://snook.ca/archives/javascript/testing_for_a_v

使用 ~ && || 等运算符 ((),()) ~~ 仅当您的代码稍后中断时才可以。你不知道从哪里开始。所以可读性很大。

如果你必须,你可以缩短它。

('a' in oc(['a','b','c'])) && statement;
('a' in oc(['a','b','c'])) && (statements,statements);
('a' in oc(['a','b','c']))?statement:elseStatement;
('a' in oc(['a','b','c']))?(statements,statements):(elseStatements,elseStatements);

如果你想做逆

('a' in oc(['a','b','c'])) || statement;
于 2013-08-21T01:09:17.293 回答
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

于 2013-08-27T20:47:55.347 回答
2

对于非常长的字符串列表,这个想法会节省一些字符(并不是说我会在现实生活中推荐它,但它应该可以工作)。

选择一个您知道不会出现在 test.type 中的字符,将其用作分隔符,将它们全部粘贴到一个长字符串中并搜索:

if ("/itema/itemb/itemc/itemd/".indexOf("/"+test.type+"/")>=0) {
  // doSomething
}

如果您的字符串碰巧受到进一步限制,您甚至可以省略分隔符...

if ("itemaitembitemcitemd".indexOf(test.type)>=0) {
  // doSomething
}

...但在这种情况下你必须小心误报(例如,“embite”会在那个版本中匹配)

于 2013-08-27T21:16:58.033 回答
2

为了可读性,为测试创建一个函数(是的,一个单行函数):

function isTypeDefined(test) {
    return test.type == 'itema' ||
           test.type == 'itemb' ||
           test.type == 'itemc' ||
           test.type == 'itemd';
}

然后调用它:

…
    if (isTypeDefined(test)) {
…
}
...
于 2013-08-28T05:10:07.193 回答
1

我认为在编写这种 if 条件时有两个目标。

  1. 简洁
  2. 可读性

因此,有时#1 可能是最快的,但我会选择#2 以便稍后进行维护。根据情况,我经常会选择沃尔特答案的变体。

首先,我有一个全局可用的函数作为我现有库的一部分。

function isDefined(obj){
  return (typeof(obj) != 'undefined');
}

然后当我真的想运行一个类似于你的 if 条件时,我会创建一个包含有效值列表的对象:

var validOptions = {
  "itema":1,
  "itemb":1,
  "itemc":1,
  "itemd":1
};
if(isDefined(validOptions[test.type])){
  //do something...
}

它不像 switch/case 语句那么快,并且比其他一些示例更冗长,但我经常在代码的其他地方重用该对象,这非常方便。

在上面制作的一个 jsperf 示例上,我添加了这个测试和一个变体来比较速度。http://jsperf.com/if-statements-test-techsin/6我注意到的最有趣的事情是,Firefox 中的某些测试组合甚至比 Chrome 快得多。

于 2013-08-28T00:38:07.087 回答
1

这可以通过一个简单的 for 循环来解决:

test = {};
test.type = 'itema';

for(var i=['itema','itemb','itemc']; i[0]==test.type && [
    (function() {
        // do something
        console.log('matched!');
    })()
]; i.shift());

我们使用 for 循环的第一部分来初始化您希望匹配的参数,第二部分用于停止 for 循环运行,第三部分用于导致循环最终退出。

于 2013-08-28T03:41:00.407 回答