【问题标题】:Is there a null-coalescing (Elvis) operator or safe navigation operator in javascript?javascript 中是否有空合并 (Elvis) 运算符或安全导航运算符?
【发布时间】:2011-09-30 15:12:43
【问题描述】:

我会举例说明:

猫王运算符 (?: )

“猫王接线员”是缩写 Java的三元运算符。一 这很方便的例子是 返回一个“合理的默认”值 如果表达式解析为 false 或 空值。一个简单的例子可能看起来像 这个:

def gender = user.male ? "male" : "female"  //traditional ternary operator usage

def displayName = user.name ?: "Anonymous"  //more compact Elvis operator

安全导航运算符 (?.)

使用了安全导航运算符 避免 NullPointerException。 通常当您参考 您可能需要验证的对象 在访问之前它不为空 对象的方法或属性。 为了避免这种情况,安全导航 运算符将简单地返回 null 而不是抛出异常,比如 所以:

def user = User.find( "admin" )           //this might be null if 'admin' does not exist
def streetName = user?.address?.street    //streetName will be null if user or user.address is null - no NPE thrown

【问题讨论】:

  • C# 中存在“Elvis 运算符”——但它被称为 null 合并运算符(不那么令人兴奋):-)
  • 如果你想要另一种语法,你可以看看 cofeescript
  • 这个问题有点混乱......它混合了 3 个不同的运算符? : (三元运算符,在问题中拼写出来,可能是错字),?? (null 合并,在 JavaScript 中确实存在)和 ?. (猫王)在 JavaScript 中不存在。答案并没有很好地阐明这种区别。
  • @JoelFan 您能否提供指向有关 javascript 中正确空合并 (??) 的文档的链接?到目前为止,我发现的所有内容都表明 JS 只有“虚假”合并(使用 ||)。
  • 好吧,我不是说JS字面上有??但它有零合并......但即使在那里我也有点错。话虽如此,我已经看到很多使用 || 的 JS 代码。尽管存在错误的陷阱,但作为一个无效的合并

标签: javascript jquery groovy safe-navigation-operator


【解决方案1】:

?? 可以在 js 中使用,相当于 kotlin 中的?:

【讨论】:

    【解决方案2】:

    2020 年更新

    JavaScript 现在具有 Elvis Operator 和 Safe Navigation Operator 的等价物。


    安全的财产访问

    optional chaining operator (?.) 目前是 stage 4 ECMAScript proposal。你可以use it today with Babel

    // `undefined` if either `a` or `b` are `null`/`undefined`. `a.b.c` otherwise.
    const myVariable = a?.b?.c;
    

    logical AND operator (&&) 是处理这种情况的“旧”、更详细的方法。

    const myVariable = a && a.b && a.b.c;
    

    提供默认值

    nullish coalescing operator (??) 目前是 stage 4 ECMAScript proposal。你可以use it today with Babel。如果运算符的左侧为空值(null/undefined),它允许您设置默认值。

    const myVariable = a?.b?.c ?? 'Some other value';
    
    // Evaluates to 'Some other value'
    const myVariable2 = null ?? 'Some other value';
    
    // Evaluates to ''
    const myVariable3 = '' ?? 'Some other value';
    

    logical OR operator (||) 是一种替代解决方案其行为略有不同。如果运算符的左侧是falsy,它允许您设置默认值。请注意,下面myVariable3 的结果与上面的myVariable3 不同。

    const myVariable = a?.b?.c || 'Some other value';
    
    // Evaluates to 'Some other value'
    const myVariable2 = null || 'Some other value';
    
    // Evaluates to 'Some other value'
    const myVariable3 = '' || 'Some other value';
    

    【讨论】:

    • 这个答案需要更多的支持。 Nullish Coalescing Operator 现在处于第 4 阶段。
    • a && a.b && a.c 应该是a && a.b && a.b.c。我自己无法编辑它,因为它的变化不足以让 SO 接受,而且我不想做“改变无关紧要的事情以使其变成 6 个字符”的事情。
    • 您可以使用 [] 语法添加执行此操作的方法 - 从 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 开始,所有这些都是可能的:obj.val?.prop obj.val?.[expr] obj.arr ?.[index] obj.func?.(args)
    【解决方案3】:

    目前有一个草案规范:

    https://github.com/tc39/proposal-optional-chaining

    https://tc39.github.io/proposal-optional-chaining/

    不过,我现在喜欢使用lodash get(object, path [,defaultValue])dlv delve(obj, keypath)

    更新(截至 2019 年 12 月 23 日):

    可选链接已移至第 4 阶段

    【讨论】:

    • Lodash 让 javascript 编程更受欢迎
    • 可选链最近移至stage 4,所以我们将在 ES2020 中看到它
    【解决方案4】:

    我创建了一个包,使它更易于使用。

    NPM jsdig Github jsdig

    你可以处理简单的事情,比如和对象:

    const world = {
      locations: {
        europe: 'Munich',
        usa: 'Indianapolis'
      }
    };
    
    world.dig('locations', 'usa');
    // => 'Indianapolis'
    
    world.dig('locations', 'asia', 'japan');
    // => 'null'
    

    或者更复杂一点:

    const germany = () => 'germany';
    const world = [0, 1, { location: { europe: germany } }, 3];
    world.dig(2, 'location', 'europe') === germany;
    world.dig(2, 'location', 'europe')() === 'germany';
    

    【讨论】:

      【解决方案5】:

      是的,有! ?

      Optional chaining 处于第 4 阶段,这使您可以使用 user?.address?.street 公式。

      如果等不及发布,安装@babel/plugin-proposal-optional-chaining即可使用。 以下是适合我的设置,或者直接阅读Nimmo's article

      // package.json
      
      {
        "name": "optional-chaining-test",
        "version": "1.0.0",
        "main": "index.js",
        "devDependencies": {
          "@babel/plugin-proposal-optional-chaining": "7.2.0",
          "@babel/core": "7.2.0",
          "@babel/preset-env": "^7.5.5"
        }
        ...
      }
      
      // .babelrc
      
      {
        "presets": [
          [
            "@babel/preset-env",
            {
              "debug": true
            }
          ]
        ],
        "plugins": [
          "@babel/plugin-proposal-optional-chaining"
        ]
      }
      
      // index.js
      
      console.log(user?.address?.street);  // it works
      

      【讨论】:

      • 他问有没有,而不是你能不能加一个。考虑到它不是被要求的,我认为这不是超级有用。
      • 它达到了 ECMAScript 标准化过程的第 3 阶段。 es2020 ? -- babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
      • 我认为这个答案具有误导性。
      • 这个答案不太正确! Optional chaining 仍处于第 3 阶段,ES2020 尚未发布,甚至尚未最终确定。至少您提到了如何使用它而无需等待它发布。
      • @gazdagergo 没问题 :)。
      【解决方案6】:

      我偶尔发现以下习语很有用:

      a?.b?.c
      

      可以改写为:

      ((a||{}).b||{}).c
      

      这利用了在对象上获取未知属性返回 undefined 的事实,而不是像在 nullundefined 上那样抛出异常,因此我们在导航之前将 null 和 undefined 替换为空对象。

      【讨论】:

      • 嗯,它很难阅读,但比那种冗长的&& 方法要好。 +1。
      • 这实际上是javascript中唯一真正安全的运算符。上面提到的逻辑“或”运算符是另一回事。
      • @Filippos 你能举例说明逻辑 OR 与 && 方法的不同行为吗?我想不出有什么区别
      • 它还允许在不首先将其分配给变量的情况下导航匿名值。
      • 爱它!如果您想在可能不会返回任何结果的 array.find() 操作之后获取对象的属性,这真的很有用
      【解决方案7】:

      2019 年 9 月更新

      是的,JS 现在支持这个。 v8 即将推出可选链接 read more

      【讨论】:

      • 不太一样。 OP 是关于空合并的,但还是不错的答案。
      【解决方案8】:

      这对我来说是一个很长一段时间的问题。我必须想出一个解决方案,一旦我们得到 Elvis 操作员或其他东西,就可以轻松迁移。

      这是我用的;适用于数组和对象

      把它放在 tools.js 文件或其他东西中

      // this will create the object/array if null
      Object.prototype.__ = function (prop) {
          if (this[prop] === undefined)
              this[prop] = typeof prop == 'number' ? [] : {}
          return this[prop]
      };
      
      // this will just check if object/array is null
      Object.prototype._ = function (prop) {
          return this[prop] === undefined ? {} : this[prop]
      };
      

      用法示例:

      let student = {
          classes: [
              'math',
              'whatev'
          ],
          scores: {
              math: 9,
              whatev: 20
          },
          loans: [
              200,
              { 'hey': 'sup' },
              500,
              300,
              8000,
              3000000
          ]
      }
      
      // use one underscore to test
      
      console.log(student._('classes')._(0)) // math
      console.log(student._('classes')._(3)) // {}
      console.log(student._('sports')._(3)._('injuries')) // {}
      console.log(student._('scores')._('whatev')) // 20
      console.log(student._('blabla')._('whatev')) // {}
      console.log(student._('loans')._(2)) // 500 
      console.log(student._('loans')._(1)._('hey')) // sup
      console.log(student._('loans')._(6)._('hey')) // {} 
      
      // use two underscores to create if null
      
      student.__('loans').__(6)['test'] = 'whatev'
      
      console.log(student.__('loans').__(6).__('test')) // whatev
      
      

      我知道这会使代码有点难以阅读,但它是一个简单的单行解决方案并且效果很好。我希望它可以帮助某人:)

      【讨论】:

        【解决方案9】:

        很晚才加入,目前在第 2 阶段有一个关于可选链的提案[1],并提供了一个 babel 插件[2]。它目前不在我所知道的任何浏览器中。

        1. https://github.com/tc39/proposal-optional-chaining
        2. https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining

        【讨论】:

          【解决方案10】:

          你可以自己滚动:

          function resolve(objectToGetValueFrom, stringOfDotSeparatedParameters) {
              var returnObject = objectToGetValueFrom,
                  parameters = stringOfDotSeparatedParameters.split('.'),
                  i,
                  parameter;
          
              for (i = 0; i < parameters.length; i++) {
                  parameter = parameters[i];
          
                  returnObject = returnObject[parameter];
          
                  if (returnObject === undefined) {
                      break;
                  }
              }
              return returnObject;
          };
          

          并像这样使用它:

          var result = resolve(obj, 'a.b.c.d'); 
          

          * 如果 a、b、c 或 d 中的任何一个未定义,则结果未定义。

          【讨论】:

            【解决方案11】:

            我阅读了这篇文章 (https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript) 并使用代理修改了解决方案。

            function safe(obj) {
                return new Proxy(obj, {
                    get: function(target, name) {
                        const result = target[name];
                        if (!!result) {
                            return (result instanceof Object)? safe(result) : result;
                        }
                        return safe.nullObj;
                    },
                });
            }
            
            safe.nullObj = safe({});
            safe.safeGet= function(obj, expression) {
                let safeObj = safe(obj);
                let safeResult = expression(safeObj);
            
                if (safeResult === safe.nullObj) {
                    return undefined;
                }
                return safeResult;
            }
            

            你这样称呼它:

            safe.safeGet(example, (x) => x.foo.woo)
            

            对于在其路径中遇到 null 或 undefined 的表达式,结果将是未定义的。你可以wild修改对象原型!

            Object.prototype.getSafe = function (expression) {
                return safe.safeGet(this, expression);
            };
            
            example.getSafe((x) => x.foo.woo);
            

            【讨论】:

              【解决方案12】:

              我认为以下相当于安全导航运算符,虽然有点长:

              var streetName = user && user.address && user.address.street;
              

              streetName 将是 user.address.streetundefined 的值。

              如果您希望它默认为其他内容,您可以与上述快捷方式结合使用或提供:

              var streetName = (user && user.address && user.address.street) || "Unknown Street";
              

              【讨论】:

              • 加上一个空传播和空合并的好例子!
              • 这行得通,只是你不知道你得到的是 null 还是 undefined
              【解决方案13】:

              您可以使用逻辑“或”运算符代替 Elvis 运算符:

              例如 displayname = user.name || "Anonymous"

              但 Javascript 目前没有其他功能。如果您想要替代语法,我建议您查看CoffeeScript。它有一些与您正在寻找的类似的速记。

              例如存在运算符

              zip = lottery.drawWinner?().address?.zipcode
              

              功能快捷键

              ()->  // equivalent to function(){}
              

              性感的函数调用

              func 'arg1','arg2' // equivalent to func('arg1','arg2')
              

              还有多行 cmets 和类。显然,您必须将其编译为 javascript 或以 &lt;script type='text/coffeescript&gt;' 的形式插入页面,但它增加了很多功能:)。使用 &lt;script type='text/coffeescript'&gt; 实际上只用于开发而非生产。

              【讨论】:

              • 在大多数情况下,逻辑或并不是完全需要的东西,因为您可能希望它仅在左侧未定义时才选择右侧操作数,但在它已定义且不正确时不选择。
              • CoffeeScript 主页使用&lt;script type="text/coffeescript"&gt;
              • 虽然这回答了这个问题,但它几乎完全是关于咖啡脚本而不是 javascript,并且一半以上是关于描述与 OP 无关的咖啡脚本好处。我建议将其归结为与问题相关的内容,就像咖啡脚本的其他好处一样美妙。
              • 我要去香蕉吗?当然,user2451227 的反对意见(目前有 4 票)是无效的,因为如果表达式/左操作数被定义且错误,则同样不会选择三元的中间操作数(即带有猫王运算符的右操作数)。在这两种情况下,你都必须去x === undefined
              • 请考虑更新此内容以提及optional chaining operator, ?.,Browser support 不是我将它用于一般代码的地步,但它正朝着那个方向发展。此外,现在还有nullish coalescing operator (??),具有类似的状态。
              【解决方案14】:

              我个人使用

              function e(e,expr){try{return eval(expr);}catch(e){return null;}};
              

              例如安全获取:

              var a = e(obj,'e.x.y.z.searchedField');
              

              【讨论】:

              【解决方案15】:

              对于使用一些 mixin 的安全导航操作员来说,这是一个有趣的解决方案..

              http://jsfiddle.net/avernet/npcmv/

                // Assume you have the following data structure
                var companies = {
                    orbeon: {
                        cfo: "Erik",
                        cto: "Alex"
                    }
                };
              
                // Extend Underscore.js
                _.mixin({ 
                    // Safe navigation
                    attr: function(obj, name) { return obj == null ? obj : obj[name]; },
                    // So we can chain console.log
                    log: function(obj) { console.log(obj); }
                });
              
                // Shortcut, 'cause I'm lazy
                var C = _(companies).chain();
              
                // Simple case: returns Erik
                C.attr("orbeon").attr("cfo").log();
                // Simple case too, no CEO in Orbeon, returns undefined
                C.attr("orbeon").attr("ceo").log();
                // IBM unknown, but doesn't lead to an error, returns undefined
                C.attr("ibm").attr("ceo").log();
              

              【讨论】:

                【解决方案16】:

                我认为 lodash _.get() 可以在这里提供帮助,例如 _.get(user, 'name'),以及更复杂的任务,例如 _.get(o, 'a[0].b.c', 'default-value')

                【讨论】:

                • 我对这种方法的主要问题是,由于属性的名称是字符串,你不能再使用 100% 信任的 IDE 的重构功能
                【解决方案17】:

                这是一个简单的 elvis 运算符等价物:

                function elvis(object, path) {
                    return path ? path.split('.').reduce(function (nestedObject, key) {
                        return nestedObject && nestedObject[key];
                    }, object) : object;
                }
                
                > var o = { a: { b: 2 }, c: 3 };
                > elvis(o)
                
                { a: { b: 2 }, c: 3 }
                
                > elvis(o, 'a');
                
                { b: 2 }
                
                > elvis(o, 'a.b');
                
                2
                
                > elvis(o, 'x');
                
                undefined
                

                【讨论】:

                  【解决方案18】:

                  我有一个解决方案,可以根据您自己的需要进行定制,摘自我的一个库:

                      elvisStructureSeparator: '.',
                  
                      // An Elvis operator replacement. See:
                      // http://coffeescript.org/ --> The Existential Operator
                      // http://fantom.org/doc/docLang/Expressions.html#safeInvoke
                      //
                      // The fn parameter has a SPECIAL SYNTAX. E.g.
                      // some.structure['with a selector like this'].value transforms to
                      // 'some.structure.with a selector like this.value' as an fn parameter.
                      //
                      // Configurable with tulebox.elvisStructureSeparator.
                      //
                      // Usage examples: 
                      // tulebox.elvis(scope, 'arbitrary.path.to.a.function', fnParamA, fnParamB, fnParamC);
                      // tulebox.elvis(this, 'currentNode.favicon.filename');
                      elvis: function (scope, fn) {
                          tulebox.dbg('tulebox.elvis(' + scope + ', ' + fn + ', args...)');
                  
                          var implicitMsg = '....implicit value: undefined ';
                  
                          if (arguments.length < 2) {
                              tulebox.dbg(implicitMsg + '(1)');
                              return undefined;
                          }
                  
                          // prepare args
                          var args = [].slice.call(arguments, 2);
                          if (scope === null || fn === null || scope === undefined || fn === undefined 
                              || typeof fn !== 'string') {
                              tulebox.dbg(implicitMsg + '(2)');
                              return undefined;   
                          }
                  
                          // check levels
                          var levels = fn.split(tulebox.elvisStructureSeparator);
                          if (levels.length < 1) {
                              tulebox.dbg(implicitMsg + '(3)');
                              return undefined;
                          }
                  
                          var lastLevel = scope;
                  
                          for (var i = 0; i < levels.length; i++) {
                              if (lastLevel[levels[i]] === undefined) {
                                  tulebox.dbg(implicitMsg + '(4)');
                                  return undefined;
                              }
                              lastLevel = lastLevel[levels[i]];
                          }
                  
                          // real return value
                          if (typeof lastLevel === 'function') {
                              var ret = lastLevel.apply(scope, args);
                              tulebox.dbg('....function value: ' + ret);
                              return ret;
                          } else {
                              tulebox.dbg('....direct value: ' + lastLevel);
                              return lastLevel;
                          }
                      },
                  

                  像魅力一样工作。享受更少的痛苦!

                  【讨论】:

                  • 看起来很有希望,你能提交完整的源代码吗?你有公开的吗? (例如 GitHub)
                  • 我将从我使用它的代码中创建一个小摘录,并在一周左右将其发布到 GitHub 上。
                  【解决方案19】:

                  对于前者,您可以使用||。 Javascript“逻辑或”运算符不是简单地返回固定的真假值,而是遵循如果为真则返回其左参数的规则,否则评估并返回其右参数。当您只对真值感兴趣时,结果相同,但这也意味着 foo || bar || baz 返回 foo、bar 或 baz 中包含真值的最左边的一个。 p>

                  但是,您不会找到可以区分 false 和 null 的方法,并且 0 和空字符串是 false 值,因此请避免使用 value || default 构造,其中 value 可以合法地为 0 或 ""

                  【讨论】:

                  • 请注意,当左操作数为非空错误值时,这可能会导致意外行为。
                  【解决方案20】:

                  你可以通过以下方式达到大致相同的效果:

                  var displayName = user.name || "Anonymous";
                  

                  【讨论】:

                    【解决方案21】:

                    Javascript 的 logical OR operatorshort-circuiting 并且可以替换您的“Elvis”运算符:

                    var displayName = user.name || "Anonymous";
                    

                    但是,据我所知,没有与您的 ?. 运算符等效的选项。

                    【讨论】:

                    • +1,我忘了|| 可以这样使用。请注意,这不仅会在表达式为 null 时合并,而且还会在未定义、false0 或空字符串时合并。
                    • @Cameron,确实如此,但问题中提到了这一点,似乎是提问者的意图。 ""0 可能出乎意料:)
                    【解决方案22】:

                    这通常称为空值合并运算符。 Javascript 没有。

                    【讨论】:

                    • true 在严格意义上,但正如其他答案所指出的那样,JavaScript 的逻辑 OR 运算符可以表现为一种 false -coalescing 运算符,允许您在很多情况。
                    • 这不是一个空合并运算符。 Null-coalescing 仅适用于单个值,而不适用于属性访问/函数调用链。您已经可以使用 JavaScript 中的逻辑 OR 运算符进行空值合并。
                    • 不,您可以使用 JavaScript 中的逻辑 OR 进行错误合并。
                    猜你喜欢
                    • 2017-12-22
                    • 2010-10-03
                    • 2021-07-19
                    • 2017-03-07
                    • 1970-01-01
                    • 2016-04-07
                    • 2012-03-13
                    • 1970-01-01
                    相关资源
                    最近更新 更多