javascript 中是不是有空合并 (Elvis) 运算符或安全导航运算符?

Posted

技术标签:

【中文标题】javascript 中是不是有空合并 (Elvis) 运算符或安全导航运算符?【英文标题】: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 代码。尽管存在错误的陷阱,但作为一个无效的合并 【参考方案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]。它目前不在我所知道的任何浏览器中。

    https://github.com/tc39/proposal-optional-chaining 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)tryreturn eval(expr);catch(e)return null;;

例如安全获取:

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

【讨论】:

首先是really shouldn't use eval。其次,这甚至不起作用:e(a:b:c:d:'test', 'a.b.c.d') 返回null @Pylinux 基本上可以工作的是e = evalvar a = eval('obj.a.b.c.d')eval 甚至不需要第二个参数...developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案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 operator 是 short-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 进行错误合并。

以上是关于javascript 中是不是有空合并 (Elvis) 运算符或安全导航运算符?的主要内容,如果未能解决你的问题,请参考以下文章

如何将两个音频文件合并为一个并在javascript中合并后播放

pandas数据合并之append与concat

KVM 虚拟机 安装配置

如何在 Javascript 中合并两个替换?

KVM克隆 快照

Javascript 删除所有空的 innerHTML 子元素