【问题标题】:How to write a ternary operator (aka if) expression without repeating yourself如何在不重复自己的情况下编写三元运算符(又名 if)表达式
【发布时间】:2017-09-07 15:44:11
【问题描述】:

例如,像这样的:

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

有没有更好的写法?同样,我不是在寻求上述确切问题的答案,只是一个示例,说明您何时可能在三元运算符表达式中重复操作数...

【问题讨论】:

  • 所以if 而不是if/else
  • just an example of when you might have repeated things in the ternary 不要重复计算表达式。这就是变量的用途。
  • 我们如何确定3 不在someArray 的索引0 中?
  • 你的目标是什么?您是要减少行长,还是特别要避免在三元组中重复变量?前者是可能的,后者不是(至少,不使用三元组)。
  • 为什么不改用Math.max(someArray.indexOf(3), 0)

标签: javascript conditional-operator


【解决方案1】:

对于这种特殊情况,您可以使用逻辑 || 运算符进行短路。由于0 被认为是虚假的,您可以将1 添加到您的索引中,因此,如果index+10,那么您将得到逻辑或的右侧作为结果,否则,您会得到你的index+1。由于您想要的结果被1 抵消,因此您可以从中减去1 以获得您的索引:

const someArray = [1, 2, 3, 4];
const v = ((someArray.indexOf(3)+1) || 1)-1;
console.log(v);

【讨论】:

    【解决方案2】:

    代码应该是可读的,所以简洁并不意味着不惜一切代价保持简洁——因为你应该重新发布到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:自执行匿名函数

    另一种选择是自执行匿名函数:

    // 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 : x 来简化上述所有示例以方便阅读:

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

    【讨论】:

    • 所有这些都是那种“聪明”的编码,让我盯着它看了几秒钟,然后想“嗯,我想这行得通”。它们无助于清晰,而且由于阅读的代码比编写的多,因此更重要。
    • @g.rocket 方法 1 是 100% 可读且清晰的,如果您想避免重复,这是唯一的方法(如果您调用一些复杂且有问题的函数而不是一个简单的indefOf)
    • 同意,#1 的可读性和可维护性很好,其他两个就没那么多了。
    • 对于#3,我相信你可以简化为var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);而不是使用逗号。
    • 在第二个例子中自执行匿名函数你可以省略箭头函数的括号。 var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) ); 因为只有一个参数。
    【解决方案3】:

    编辑:在这里,Nullary-coalescing 的提案现在在 JavaScript 中!


    使用||

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

    等价于

    const result = a || 'fallback value';

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


    注意极端情况a === 0,它转换为falseresult 将(错误地)采用'fallback value'。使用此类技巧需要您自担风险。


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

    【讨论】:

    • PHP (>7.0) 和 C# 也支持空值合并运算符。语法糖,但肯定很可爱。
    • 这仅适用于函数在失败时返回错误值的情况,但indexOf()不能用于此模式。
    • 正确,但他并没有要求具体的例子>“再次,不寻求上述确切问题的答案,只是一个例子,说明你何时可能在三元中重复了一些事情”
    • 真的||在JavaScript中是如何工作的吗?如果我对您的理解正确,那么它的工作方式与许多其他主要语言不同(我主要考虑 C 及其后代 [C++、Java 等])即使这确实是 || 在 JavaScript 中的工作方式,我也会建议不要使用这样的技巧,因为这些技巧需要维护人员了解该语言的特殊怪癖。虽然这个技巧很酷,但我认为这是不好的做法。
    • 另请注意,问题是将值与-1 进行比较。同样,我不能代表 JavaScript 及其怪癖,但通常 -1 将是一个真值,而不是一个假值,因此您的答案在问题的情况下不起作用,当然也不是一般情况,而是只适用于一个特定(但足够常见)的子案例。
    【解决方案4】:

    三元就像一个 if-else,如果你不需要 else 部分,为什么不只是一个 if 代替..

    if ((value = someArray.indexOf(3)) < 0) value = 0;
    

    【讨论】:

      【解决方案5】:

      这是一个简单的解决方案,使用 bitwise NOT 和默认值 -1,稍后会为零。

      index = ~(~array.indexOf(3) || -1);
      

      它基本上适用于双位非,它返回原始值或默认值,应用位非后返回零。

      让我们来看看真值表:

       indexOf    ~indexOf   boolean    default     value      result         comment
      ---------  ---------  ---------  ---------  ---------  ---------  ------------------
            -1          0     falsy          -1         -1          0   take default value
             0         -1    truthy                     -1          0
             1         -2    truthy                     -2          1
             2         -3    truthy                     -3          2
      

      【讨论】:

        【解决方案6】:

        使用extract variable refactoring

        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;
        

        【讨论】:

        • 什么是“提取变量”?这是一个既定的术语吗?
        【解决方案7】:

        我可以通过两种方式查看您的问题:您要么想减少行长,要么特别想避免在三元组中重复变量。第一个是微不足道的(许多其他用户已经发布了示例):

        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为falsey时使用||返回RHS:

        var value = foo || bar; // equivalent to !foo ? bar : foo
        

        【讨论】:

        • 这个问题被标记为 javascript 并且没有提到 C#。只是想知道为什么以 C# 特定示例结尾。
        • 我错过了问题上的 javascript 标签;删除了 C#。
        【解决方案8】:

        我个人更喜欢两种变体:

        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,选项2尊重问题的内联意图,可以有效地应用于javascript以外的其他语言,并促进模块化。
        【解决方案9】:

        我认为||运算符可以定制为indexOf

        var value = ((someArray.indexOf(3) + 1) || 1) - 1;
        

        返回值上移 1,从 -1 变为 0,这是错误的,因此被第二个 1 替换。然后它被移回。

        但是,请记住,可读性优于避免重复。

        【讨论】:

          【解决方案10】:

          使用辅助函数:

          function translateValue(value, match, translated) {
             return value === match ? translated : value;
          }
          

          现在你的代码可读性很强,没有重复。

          var value = translateValue(someArray.indexOf(3), -1, 0);
          

          编码关注点的层次结构是:

          1. 正确(包括真正的性能或 SLA 问题)
          2. 清除
          3. 简洁
          4. 快速

          到目前为止,页面上的所有答案似乎都是正确的,但我认为我的版本具有最高的清晰度,这比简洁更重要。如果你不计算辅助函数——因为它可以被重用——它也是最简洁的。不幸的是,使用辅助函数的有点类似的建议使用了一个 lambda,对我来说,它只是掩盖了它在做什么。一个更简单的函数,它的目的是不使用 lambda,只使用值,对我来说要好得多。

          附:如果你喜欢 ES6 语法:

          const translateValue = (value, match, translated) => value === match ? translated : value;
          let value = translateValue(someArray.indexOf(3), -1, 0); // or const
          

          【讨论】:

          • “我的版本清晰度最高” - 我不同意。函数名太长,命名参数 inputoutput 根本没有帮助。
          • 你能推荐更好的名字吗?我很乐意招待他们。您的投诉完全是关于化妆品的,所以让我们修理化妆品吧。正确的?否则你只是自行车脱落。
          • 在某些情况下,这样的辅助函数会很有用。在这种情况下,它仅替换三元运算符的特定情况,您的功能将不太清楚。我不会记住你的函数做了什么,每次遇到它我都得再去查找它,我永远不会记得使用它。
          • @Aaron 这是对特定用例的合理评估。值得一提的是,我最初的函数名称是 translateValueIfEqual,我认为它更具描述性,但在有人认为它太长后我更改了它。很像 Access 中的Nz 函数,如果你知道它,你就知道它,如果你不知道,你就不知道。在现代 IDE 中,您只需按一个键即可跳转到定义。而后备将是中间变量。我真的没有看到这里有很大的缺点。
          • 这只是表明,如果您向 5 个不同的工程师提出同一个问题,您将得到 10 个不同的答案。
          【解决方案11】:

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

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

          然后打电话

          var value = get_value( someArray );
          

          如果您在其他地方使用它们,您可以做更多通用功能,但如果是非常具体的情况,请不要过度设计。

          但老实说,除非我需要在多个地方重复使用,否则我只会像 @slebetman 那样做。

          【讨论】:

            【解决方案12】:

            您可能正在寻找合并运算符。幸运的是,我们可以利用 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); 打印 0,1,"coalesce"
            • @CharlieHarding 是的,但一般不建议在遍历数组时使用 for-in 运算符。见stackoverflow.com/a/4374244/1486100
            【解决方案13】:

            不是真的,只是使用另一个变量。

            您的示例可以概括为这样的内容。

            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)
            

            【讨论】:

              【解决方案14】:

              对于数字

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

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

              如果是这种情况,它将保持结果的边界从0 直到第一个结果大于0。如果indexOf 的结果是-1,它将返回大于-1 的0。

              对于布尔值和布尔值

              对于 JS,AFAIK 没有特别的通用规则,因为如何评估 falsy 值。

              但如果大多数时候可以帮助您的是 or 运算符 (||):

              // 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,因为0falsy 值。

              【讨论】:

              • 谢谢。我想我的例子很糟糕,我只是举一个一般的例子哈哈,而不是寻求确切问题的解决方案。我遇到了一些 scnarios,例如我想使用三元的示例,但最终重复了:(
              • 第一个例子,我们如何确定3"y" 不在someArray 的索引0 中?
              • Math.max 示例中? indexOf 返回元素的索引,如果未找到该元素返回 -1,因此您有机会从 -1 获取到字符串长度的数字,然后使用 Math.max 您只需将边界设置为 0 到消除返回 -1 机会的长度,
              • @mitogh OP 代码中的逻辑造成了一个问题,尽管它是通用示例;其中 0 既可以表示数组中匹配元素的索引0,也可以表示0 设置为Math.max();或在 OP 的条件运算符处。考虑var value = Math.max( ["y"].indexOf("y"), 0 )。您如何确定返回的是哪个00 传递给Math.max() 调用,或者0 反映数组中"y" 的索引?
              • @guest271314 好主意,但我认为这是否是一个问题取决于上下文。也许 0 来自哪里并不重要,唯一重要的是它不是 -1。一个例子:也许你需要从一个数组中挑选一个项目。你想要一个特定的项目(在 OP 中,数字 3),但如果它不在数组中,你仍然需要一个项目,并且你可以默认为第一个项目是什么,假设你知道数组不是t 为空。
              【解决方案15】:

              鉴于问题中的示例代码,尚不清楚如何确定3 是否设置在someArray 的索引0 处。从.indexOf() 返回的-1 在这种情况下很有价值,目的是排除可能是匹配的假定不匹配。

              如果数组中不包含3,则返回-1。我们可以将1 添加到.indexOf() 的结果中,以评估为false,结果为-1,然后是|| OR 运算符和0。当引用value 时,减去1 得到数组元素的索引或-1

              这导致简单地使用.indexOf() 并在if 条件下检查-1。或者,将value 定义为undefined 以避免可能混淆与原始参考相关的评估条件的实际结果。

              var someArray = [1,2,3];
              var value = someArray.indexOf(3) + 1 || 1;
              console.log(value -= 1);
              
              var someArray = [1,2,3];
              var value = someArray.indexOf(4) + 1 || 1;
              // how do we know that `4` is not at index `0`?
              console.log(value -= 1);
              
              var someArray = [1,2,3];
              var value = someArray.indexOf(4) + 1 || void 0;
              // we know for certain that `4` is not found in `someArray`
              console.log(value, value = value || 0);

              【讨论】:

                【解决方案16】:

                您可以使用重新分配:

                • 将变量初始化为一个值
                • 使用&amp;&amp; 运算符的序列化进行重新赋值,因为如果第一个条件为假,则不会计算第二个表达式

                例如

                var value = someArray.indexOf(3);
                value == -1 && (value=0);
                

                var someArray = [4,3,2,1];
                
                var value = someArray.indexOf(1);
                value == -1 && (value=0);
                console.log('Found:',value);
                
                var value = someArray.indexOf(5);
                value == -1 && (value=0);
                console.log('Not Found:',value);

                【讨论】:

                • @MinusFour 在一定程度上。变量是重复的,不是表达式someArray.indexOf只执行一次
                • 你重复value
                • @MinusFour 正确,但这对于较大的表达式更有用,与保存操作相比,重复变量是微不足道的。我的猜测是 OP 不适用于 -10;否则max() 将是最好的选择
                • 对...但问题是...“如何编写三元组不重复自己”
                • 它还写着Again, not seeking an answer to the exact question above,这将问题留给了解释;)
                【解决方案17】:

                我个人认为最好的方法仍然是旧的if 声明:

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

                【讨论】:

                • 要明确,这个答案提倡使用变量来存储中间结果,而不是使用if 语句代替三元组。三元语句在作为表达式的一部分执行选择时仍然经常有用。
                • @Triptych:再看一遍。它根本不使用中间变量。相反,它将结果直接分配给最终变量,然后在满足条件时覆盖它。
                • 不正确。第一行之后存储在value 中的值是中间值。它不包含正确的值,然后将其固定在下一行,因此是中间值。只有在if 语句得出结论value 包含正确的值之后。 OP 中的三元组是一个更好的解决方案,因为它永远不会进入中间状态。
                • @JackAidley:“OP 中的三元组是一个更好的解决方案,因为它永远不会进入中间状态。” - 我将不得不不同意。这比 OP 的代码可读性强得多,而且完全是惯用的。它还使 OP 逻辑中的错误对我来说更加明显(即,如果 indexOf() 返回零会发生什么?如何区分“真实”零和“未找到”零?)。
                • 这和我会做的差不多,只是这里的value 在技术上发生了变异,我尽量避免。
                猜你喜欢
                • 1970-01-01
                • 2020-02-13
                • 2020-03-05
                • 2019-08-31
                • 2021-12-18
                • 2015-06-05
                • 2019-10-13
                • 2020-07-31
                相关资源
                最近更新 更多