如何在不重复自己的情况下编写三元运算符(又称if表达式)

108
例如,像这样的东西:
var value = someArray.indexOf(3) !== -1 ? someArray.indexOf(3) : 0

有更好的写法吗?再次声明,我不是在寻求上面问题的确切答案,而是想要一个示例,说明何时可能会在三元运算符表达式中重复使用操作数...


2
所以是 if 而不是 if/else - zer00ne
53
只是一个例子,说明在三元表达式中何时可能会重复某些内容。不要重复计算的表达式,这就是变量存在的意义。 - vol7ron
4
我们如何确定数字3不在数组 someArray 的索引0处? - guest271314
3
你的目标是什么?你是想减少行长度,还是特别要避免在三元操作符中重复使用变量?前者可以做到,而后者不行(至少不能使用三元操作符)。 - asgallant
5
为什么不使用 Math.max(someArray.indexOf(3), 0) 呢? - 301_Moved_Permanently
显示剩余8条评论
17个回答

176

就我个人而言,我认为最好的方法仍然是使用老式的if语句:

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}

32
要明确的是,这个答案主张使用变量来存储中间结果,而不是使用if语句代替三元运算符。三元运算符仍然经常用于作为表达式的一部分进行选择。 - Kenan Banks
5
再看一遍。它根本没有使用中间变量。相反,它直接将结果赋值给最终变量,如果条件满足则覆盖它。 - slebetman
14
不正确。第一行存储在“value”中的值是中间值。它不包含正确的值,下一行对其进行修正,并因此成为中间值。只有在“if”语句结束后,“value”才包含正确的值。与此相比,OP中的三元运算符是更好的解决方案,因为它从未进入中间状态。 - Jack Aidley
45
@JackAidley:“OP中的三元运算符比这个更好,因为它从不进入中间状态。” - 我必须不同意。这段代码比OP的代码更易读,并且完全符合惯用语。它还让我更容易发现OP逻辑中的一个错误(即,如果indexOf()返回零会发生什么?如何区分“真正”的零和“未找到”的零?)。 - Kevin
2
这与我的做法接近,只是这里的 value 技术上被改变了,而我尽量避免这种情况。 - Dave Cousineau
显示剩余2条评论

107

代码应该易读,因此简洁并不意味着无论代价如何都应该变得过于简略 - 为了做到这一点,您应该转发到https://codegolf.stackexchange.com/ - 因此,我建议使用第二个本地变量命名为index以最大化阅读理解能力(同时尽可能减少运行时间成本):

var index = someArray.indexOf( 3 );
var value = index == -1 ? 0 : index;

但如果你真的想要简化这个表达式,因为你对同事或项目合作者是一个残忍的虐待狂,那么你可以使用以下4种方法:

1:在 var 语句中使用临时变量

你可以利用 var 语句定义(并赋值)第二个临时变量 index 的能力,用逗号分隔:

var index = someArray.indexOf(3), value = index !== -1 ? index: 0;

2:立即调用的函数表达式(IIFE)

另一个选择是立即在定义后调用的匿名function

// Traditional syntax:
var value = function( x ) { return x !== -1 ? x : 0 }( someArray.indexOf(3) );

// ES6 syntax:
var value = ( x => x !== -1 ? x : 0 )( someArray.indexOf(3) );

3:逗号运算符

JavaScript还支持臭名昭著的“逗号运算符”,该运算符也存在于C和C++中。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

当您想在需要单个表达式的位置中包含多个表达式时,可以使用逗号运算符。

您可以使用它来引入副作用,在这种情况下通过重新分配给value

var value = ( value = someArray.indexOf(3), value !== -1 ? value : 0 );

这是因为var value首先被解释执行(因为它是一条语句),然后是最左侧、最内层的value赋值,再接着是逗号运算符的右操作数以及三元运算符——所有这些都是合法的 JavaScript 代码。

4:在子表达式中重新赋值

评论家@IllusiveBrian指出,如果将对value的赋值用作括号化的子表达式,则无需使用逗号运算符(在前一个示例中):

var value = ( ( value = someArray.indexOf(3) ) !== -1 ? value : 0 );
注意,在逻辑表达式中使用否定词可能会让人们更难理解 - 因此以上所有示例都可以通过将 idx !== -1 ? x : y 更改为 idx == -1 ? y : xidx < 0 ? y : x 来简化阅读。
var value = ( ( value = someArray.indexOf(3) ) == -1 ? 0 : value );

99
这些都是那种会让我盯着看几秒钟才会想到“哦,原来可以这样”的“巧妙”编码方式。它们并没有帮助提高清晰度,因为代码被阅读的次数比被编写的次数多,所以清晰度更为重要。 - Gavin S. Yancey
18
@g.rocket 的第一种方法读起来非常清晰明了,如果你想避免重复(如果你正在调用一些复杂和有问题的函数而不是简单的 "indexOf",那么重复可能会非常有害),那么这是唯一可行的方法。 - edc65
5
同意,第一种方法非常易读和易于维护,另外两种方法则不太如此。 - forgivenson
6
对于第三个问题,我认为你可以简化为 var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value); 而不是使用逗号。 - IllusiveBrian
2
在第二个例子中,您可以从箭头函数中省略括号来执行自我匿名函数 _Self-executing anonymous function_。由于只有一个参数,因此可以这样写:var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) ); - Alex Char
显示剩余10条评论

54

关于数字

你可以使用 Math.max() 函数。

var value = Math.max( someArray.indexOf('y'), 0 );

如果结果大于等于0,则该函数将返回从0到第一个大于0的结果边界。如果indexOf的结果为-1,则它将返回0,因为0大于-1

对于布尔值和类布尔值

对于JS来说,我认为没有通用规则,特别是由于如何评估Falsy的值。

但如果有些东西能帮助你大部分时间,那就是或运算符(||):

// Instead of
var variable = this_one === true ? this_one : or_this_one;
// you can use
var variable = this_one || or_this_one;

你必须非常谨慎,因为在第一个例子中,indexOf 可能会返回 0 ,如果你评估 0 || -1 ,它将返回 -1,因为 0 是一个假值


2
谢谢。我猜我的例子不太好,只是举了一个普通的例子哈哈,并不是在寻求确切问题的解决方案。我遇到过一些像例子中那样的情况,我想使用三元运算符,但最终却重复了:( - user1354934
1
在第一个例子中,我们如何确定 3"y" 不在 someArray 的索引 0 处? - guest271314
Math.max的例子中,indexOf返回元素的索引,如果未找到元素,则返回-1,因此您有机会从-1到字符串长度获得数字,然后使用Math.max将边界设置为0到长度以消除返回-1的可能性。 - Crisoforo Gaspar
@mitogh OP代码中的逻辑存在问题,尽管这只是一个通用示例;其中0既可以表示匹配元素数组中的索引0,也可以表示在OP中设置的Math.max()中的0;或者在条件运算符中。请考虑var value = Math.max( ["y"].indexOf("y"), 0 )。如何确定返回哪个0?是传递给Math.max()调用的0,还是反映数组中"y"的索引的0 - guest271314
@guest271314 不错的想法,但我认为这取决于上下文是否是一个问题。也许0来自哪里并不重要,唯一重要的是它不是-1。例如:也许您需要从数组中选择一个项目。您想要特定的项目(在OP中,数字3),但如果它不在数组中,您仍然需要一个项目,并且您可以默认为第一个项目,假设您知道该数组不为空。 - Kev
@isanae 它将哨兵值转换回域值。在我看来很合理。 - ErikE

27

不用担心,只需使用另一个变量即可。

你的示例可以推广到类似以下的内容。

var x = predicate(f()) ? f() : default;

如果您正在测试一个计算出来的值,然后根据某些谓词为其赋值给一个变量,那么避免重新计算这个计算出来的值的方法是显而易见的:使用一个变量来存储结果。

var computed = f();
var x = predicate(computed) ? computed : default;

我了解你的意思 - 看起来应该有某种更简洁的方法来实现这个目标。但我认为这是最佳方式(习惯用语)来做到这一点。如果出于某种原因在代码中频繁重复这种模式,你可以编写一个小帮助函数:

var setif = (value, predicate, default) => predicate(value) ? value : default;
var x = setif(someArray.indexOf(3), x => x !== -1, 0)

20

编辑:在JavaScript中现在有了Nullary-coalescing提案!


使用||

const result = a ? a : 'fallback value';

等同于

const result = a || 'fallback value';

如果将a转换为Boolean返回false,则result将被分配为'fallback value',否则分配a的值。


请注意边缘情况a === 0,它会被转换为false,并且result将(不正确地)取'fallback value'。使用这样的技巧需自担风险。


PS。像Swift这样的语言有nil-coalescing运算符(??),其作用类似。例如,在Swift中,您可以编写result = a ?? "fallback value",这与JavaScript的const result = a || 'fallback value';非常接近


3
PHP(>7.0)和C#也支持null合并运算符。这是一种语法糖,但确实非常方便。 - Hissvard
5
只有当函数返回一个假值时才能使用这种方法,但 indexOf() 无法在这种模式下使用。 - Barmar
1
正确,但他并不是在寻求特定示例的答案 >“再次强调,他并不是在寻求上述问题的确切答案,而是要求提供一个替换重复三元运算符(result = a ? a : b)的通用方法。”< 请注意,重复的三元运算符等同于 ||(result = a || b)。 - Lyubomir
2
这真的是 JavaScript 中 || 运算符的工作原理吗?如果我理解你的意思正确的话,那它与许多其他主要编程语言(主要是 C 及其派生语言 [C++, Java 等])的工作方式不同。即使这确实是 JavaScript 中 || 的工作方式,我建议不要使用像这样的技巧,因为这需要维护人员了解关于该语言的特殊怪异之处。虽然这个技巧很酷,但我认为这是一种不良实践。 - Loduwijk
1
还要注意的是,问题将值与“-1”进行比较。再次强调,我不能代表JavaScript及其怪癖,但通常“-1”将是一个真值,而不是一个假值,因此您的答案在问题的情况下不起作用,当然也不适用于一般情况,而只适用于特定(但足够常见)的子情况。 - Loduwijk
如果我能使用时间机器来调整C语言的一些早期设计特性,我会让||返回第一个操作数(如果为真),否则返回第二个操作数,并使&&返回第一个操作数(如果为假),否则返回第二个操作数。将值强制转换为整数0和1需要编译器生成额外的代码,或者通过额外的努力避免这样做,而不是简单地让运算符返回已计算的值之一。JS通过更改行为与C相比做得很对。 - supercat

8

使用提取变量重构

var index = someArray.indexOf(3);
var value = index !== -1 ? index : 0

使用 const 而不是 var 更好。你也可以进行额外的提取:

const index = someArray.indexOf(3);
const condition = index !== -1;
const value = condition ? index : 0;

在实践中,使用比indexconditionvalue更有意义的名称。
const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;

“提取变量”是什么?这是一个已经确立的术语吗? - Peter Mortensen

6

我个人更喜欢两种变体:

  1. 如果条件纯粹,就像@slebetman建议的那样

  2. 分离函数,可以将无效值替换为默认值,就像这个例子中的代码:

function maskNegative(v, def) {
  return v >= 0 ? v : def;
}

Array.prototype.indexOfOrDefault = function(v, def) {
  return maskNegative(this.indexOf(v), def);
}

var someArray = [1, 2];
console.log(someArray.indexOfOrDefault(2, 0)); // index is 1
console.log(someArray.indexOfOrDefault(3, 0)); // default 0 returned
console.log(someArray.indexOfOrDefault(3, 123)); // default 123 returned


1
+1,选项2尊重问题的内联意图,可以高效地应用于除JavaScript之外的其他语言,并促进模块化。 - Devsman

6

您可能正在寻找一个合并运算符。幸运的是,我们可以利用Array原型来创建一个:

Array.prototype.coalesce = function() {
    for (var i = 0; i < this.length; i++) {
        if (this[i] != false && this[i] != null) return this[i];
    }
}

[null, false, 0, 5, 'test'].coalesce(); // returns 5

这可以进一步推广到您的情况,通过向函数添加参数来实现:
Array.prototype.coalesce = function(valid) {
    if (typeof valid !== 'function') {
        valid = function(a) {
            return a != false && a != null;
        }
    }

    for (var i = 0; i < this.length; i++) {
        if (valid(this[i])) return this[i];
    }
}

[null, false, 0, 5, 'test'].coalesce(); // still returns 5
[null, false, 0, 5, 'test'].coalesce(function(a){return a !== -1}); // returns null
[null, false, 0, 5, 'test'].coalesce(function(a){return a != null}); //returns false

向数组的原型添加内容是有风险的,因为新元素会成为每个数组内部的索引。这意味着遍历索引时也包括了新方法:for (let x in ['b','c']) console.log(x); 输出 01"coalesce" - Charlie Harding
@CharlieHarding 是的,但通常不建议在循环数组时使用for-in运算符。请参见https://dev59.com/inA75IYBdhLWcg3w8-LZ#4374244 - Tyzoid

5

我喜欢@slebetman的回答。下面的评论表达了对变量处于“中间状态”的担忧。如果这对你来说是一个大问题,那么我建议将其封装在一个函数中:

function get_value(arr) {
   var value = arr.indexOf(3);
   if (value === -1) {
     value = 0;
   }
   return value;
}

然后只需要调用。
var value = get_value( someArray );

如果在其他地方需要使用,您可以编写更通用的函数,但如果是非常特定的情况,请不要过度设计。

但说实话,除非需要从多个地方重复使用,否则我会像@slebetman一样操作。


4

我看到你的问题有两种解释:你要么想要减少行长度,要么想要在三元运算中避免重复变量。第一种方法很简单(许多其他用户已经发布了示例):

var value = someArray.indexOf(3) !== -1 ? someArray.indexOf(3) : 0;

可以(而且应该,考虑到函数调用)像这样缩短:
var value = someArray.indexOf(3);
value = value !== -1 ? value : 0;

如果您正在寻找一种更通用的解决方案,以防止三元运算中变量的重复,例如:
var value = conditionalTest(foo) ? foo : bar;

foo只出现一次。排除以下形式的解决方案:

var cad = foo;
var value = conditionalTest(foo) ? cad : bar;

如果你认为技术上正确但是没抓住重点,那么你就没那么幸运了。有些操作符、函数和方法拥有你所寻求的简洁语法,但这样的结构,根据定义,并不是三元操作符。
例如:
javascript中,使用||在LHS为假值时返回RHS:
var value = foo || bar; // equivalent to !foo ? bar : foo

1
该问题被标记为JavaScript,没有提到C#。只是想知道为什么你最后用了C#的特定示例。 - Loduwijk
我在问题中错过了JavaScript标签;删除了C#。 - asgallant

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