javascript中是否有空合并(Elvis)操作符或安全导航操作符?

我将举例解释:

猫王运算符(?:)

“Elvis运算符”是缩写 Java的三元运算符。一个 这很方便的例子是 返回一个“合理的默认值” 如果表达式解析为false或 null。一个简单的例子是这样的 : < / p >

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


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

安全导航操作员(?.)

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

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
156607 次浏览

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

Javascript的逻辑或运算符短路,可以替换你的“Elvis”操作符:

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

然而,据我所知,没有与你的?.操作符等价的东西。

你可以这样说来达到大致相同的效果:

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

对于前者,你可以使用||。Javascript的“逻辑或”操作符,不是简单地返回罐装的真值和假值,而是遵循这样的规则:如果左参数为真,则返回左参数,否则计算并返回右参数。当你只对真值感兴趣时,结果是一样的,但这也意味着foo || bar || baz返回foo、bar或baz中最左边的一个,它包含一个真值

不过,你找不到一个可以区分false和null的函数,而且0和空字符串是假值,所以避免使用value || default构造,其中value可以合法地为0或""

你可以使用逻辑'OR'操作符来代替Elvis操作符:

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

但Javascript目前还不具备其他功能。如果你想要另一种语法,我建议你看看CoffeeScript。它有一些类似于你要找的东西的简写。

例如存在操作符

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

功能快捷键

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

性感的函数调用

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

还有多行注释和类。显然,你必须将其编译成javascript或作为<script type='text/coffeescript>'插入到页面中,但它添加了很多功能:)。使用<script type='text/coffeescript'>实际上只是为了开发而不是生产。

我有一个解决方案,根据你自己的需要来调整,摘自我的一篇文章:

    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;
}
},

效果非常好。享受较少的痛苦!

你可以自己卷:

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

这是一个简单的猫王运算符等价:

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

我认为下面是相当于安全导航的操作符,虽然有点长:

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

streetName将是user.address.streetundefined的值。

如果你想让它默认为其他东西,你可以结合上面的快捷方式或给出:

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

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

我偶尔会发现下面这个习语很有用:

a?.b?.c

可以改写为:

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

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

这是一个有趣的解决方案,安全导航操作员使用一些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();

我个人使用

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

例如,safe get:

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

目前有一个规范草案:

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

< a href = " https://tc39.github。Io /proposal-optional- chains /" rel="nofollow noreferrer">https://tc39.github.io/proposal-optional-chaining/ .

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

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

可选链接已经移动到阶段4

我读了这篇文章(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原型!

Object.prototype.getSafe = function (expression) {
return safe.safeGet(this, expression);
};


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

在很晚的时候,有一个建议[1]的可选链接目前在第二阶段,与巴别塔插件[2]可用。目前在我所知道的任何浏览器中都没有。

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

是的,有!🍾

可选的链接在第4阶段,这允许你使用user?.address?.street公式。

如果你不能等待发布,安装@babel/plugin-proposal-optional-chaining,你就可以使用它。 这是我的设置,适用于我,或只需阅读尼姆的文章
// 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

这个问题困扰了我很长一段时间。我必须想出一个解决方案,一旦我们得到猫王运算符或其他东西,就可以很容易地迁移。

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

把这个放到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


好吧,我知道这使代码有点难以阅读,但这是一个简单的一行解决方案,工作出色。我希望它能帮助到一些人:)

2019年9月更新

是的,JS现在支持这个。 v8 阅读更多

即将加入可选链接

2020年更新

JavaScript现在有了Elvis操作符和安全导航操作符的等价物。


安全出入物业

可选链操作符 (?.)目前是一个第四阶段 ECMAScript 建议。你可以今天用它来对付巴别塔

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

逻辑与运算符 (&&)是处理此场景的“旧”、更详细的方法。

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

提供默认值

空合并运算符 (??)目前是一个第四阶段 ECMAScript 建议。你可以今天用它来对付巴别塔。它允许你在操作符左边为空值(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';

逻辑或运算符 (||)是一个替代解决方案行为略有不同。如果运算符的左边是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';

我创建了一个软件包,使它更容易使用。

NPM jsdig Github jsdig < / p >

你可以处理简单的东西,比如and object:

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';

??将在js中工作,这相当于kotlin中的?: