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

276

我会通过示例来解释:

Elvis操作符(?: )

"Elvis操作符"是Java三元运算符的缩写。其中一个方便的实例是,如果表达式解析为false或null,则返回“合理默认值”。一个简单的示例可能如下所示:

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

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

安全导航运算符(?.)
安全导航运算符用于避免空指针异常。通常情况下,当您引用一个对象时,您可能需要在访问对象的方法或属性之前验证它不为空。为了避免这种情况,安全导航运算符将简单地返回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

11
“Elvis 操作符” 在 C# 中存在,但它被称为 null 合并运算符(相对不那么令人兴奋) :-) - Cameron
5
这个问题有点混乱... 它混淆了三个不同的运算符?:(三目运算符,在问题中拼写出来,可能是一个拼写错误),??(空值合并,JavaScript 中确实存在)和?.(Elvis 运算符),这在 JavaScript 中是不存在的。回答没有很好地澄清这个区别。 - JoelFan
3
@JoelFan,你能提供有关JavaScript中正确使用null合并(??)的文档链接吗?到目前为止,我找到的所有内容都表明JS仅具有“falsey”合并(使用||)。 - Charles Wood
@JoelFan 感谢您的跟进。我猜当您说JS有 ?? 时,那是一个打字错误 :D - Charles Wood
1
好的,我并不是说JS字面上有 ?? 这个运算符,而是它有null-coalesce...但即使在那里,我也有点错了。话虽如此,我已经看到了很多使用||作为null coalesce的JS代码,尽管存在falsey pitfalls。 - JoelFan
显示剩余6条评论
22个回答

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

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

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

5

2019年9月更新

是的,JavaScript现在支持此功能。 可选链即将到来v8 了解更多


不完全相同。OP在谈论空值合并,但仍是一个不错的答案。 - Maxie Berkmann

3

这更常被称为null合并运算符。Javascript没有这个。


3
严格来说是真的,但正如其他回答所指出的,JavaScript 的逻辑 OR 运算符可以表现为一种“假-合并”运算符,使您能够在许多情况下实现相同的简洁性。 - Shog9
1
这不是空值合并运算符。空值合并仅适用于单个值,而不适用于一系列属性访问/函数调用。在JavaScript中,您已经可以使用逻辑OR运算符执行null-coalescing操作。 - user19302
不,你可以在JavaScript中使用逻辑OR进行false-coalescing。 - andresp

2

我有一个解决方案,可以根据您自己的需求进行定制,以下是我其中一个库的摘录:

    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)发布它? - Eran Medan
1
我将从我使用的代码中创建一个小节,并在一周左右将其发布到GitHub上。 - balazstth

2
你可以自己动手制作:

你可以自己动手制作:

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中有任何一个未定义,则结果为未定义。


1

??在JavaScript中可以使用,它相当于Kotlin中的?:


1

1

这对我来说是一个长期存在的问题。我必须想出一种解决方案,一旦我们获得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

虽然我知道这会让代码有点难读,但这是一个简单的一行解决方案,而且效果很好。我希望它能对某些人有所帮助 :)


1
我阅读了这篇文章(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 时,结果将为未定义。你可以疯狂地修改对象原型!
Object.prototype.getSafe = function (expression) {
    return safe.safeGet(this, expression);
};

example.getSafe((x) => x.foo.woo);

0

这是一个有趣的解决方案,使用了一些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();

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