TypeScript中Angular2的Elvis运算符替换

41

在 TypeScript 中是否有类似于 Angular2 的 Elvis Operator 的运算符?我的意思是说,假设我要从对象中获取键(key):

this.myForm.name.first_name

如果first_name不存在,它将会抛出错误"first_name of undefined"

是的,我可以使用TypeScript的Ternary operator来处理这个错误,像这样:

this.myForm.name ? this.myForm.name.first_name : ''

但有时候键名变得太长了,

那么在 TypeScript 中是否有像 Angular2 中的 Elvis Operator 这样的运算符,使我可以这样使用呢?

this.myForm?.name?.first_name

3
为什么会被踩?这不是一个有效的问题吗? - Pardeep Jain
5
你在提到“安全导航运算符(?.)”。而“Elvis运算符(?:)”是一种三元缩写,如果为真,则隐式地评估第一个操作数。 - Drazen Bjelovuk
@DrazenBjelovuk 我正在寻找Elvis运算符的答案,但似乎找不到...无论是JS还是TS。 - s3c
@s3c 在 JS/TS 中,Elvis 操作符的等效操作符是 || - Drazen Bjelovuk
1
我应该自己连接这些点。尴尬 谢谢 - s3c
3个回答

39

2019年12月更新:TypeScript 3.7 引入了可选链,该运算符等同于其他语言中的安全导航运算符。ECMAScript 提案 可选链 已达到第4阶段,因此将成为ES2020规范的一部分。请参见mdn: Optional chaining以获取更多信息。


2017年7月更新:正如JGFMK在评论中指出的那样,ECMAScript 提案中有一个名为JavaScript 可选链接的提议。如果/当该提案达到第4阶段时,它将被添加到语言规范中。


TypeScript 中既没有安全导航运算符也没有 Elvis 运算符,就我所知,也没有类似的东西。

有关详情,请参见以下功能请求:建议:"安全导航运算符",即 x?.y。不予实现的解释是以下内容(在我看来,这是一个有效的理由):

目前关闭此问题。既然在表达式级别上没有任何 TypeScript 特定的需求需要这个,这种大型运算符改变应该发生在 ES 规范委员会而不是这里。

重新评估的一般限制是具体 ES 提案达到下一个阶段,或者ES委员会普遍达成共识,这一功能在很长时间内都不会出现(以便我们可以定义自己的语义,并且合理地确信它们会"胜出")。

替代这种表示法的方式可以使用逻辑AND运算符、try/catch或者像这篇文章中描述的getSafe(() => this.myForm.name.first_name)辅助函数。


谢谢您的回答,除了三元运算符之外,有没有其他的替代方案? - Pardeep Jain
1
@PardeepJain 你可以在某些属性未定义时分配空对象:this.myForm.name = this.myForm.name || {}。然后你可以确信name已定义。另一种方法是定义一个默认的数据结构,然后使用Object.assign覆盖它与你的实际数据。 - str
1
这里有一个关于编程的内容:https://github.com/tc39/proposal-optional-chaining。它仍处于提案阶段。我没有意识到这是一个Angular的调整...我以为它已经内置在Typescript中了... - JGFMK
鸭子类型是一种对象类型的运行时类型检查形式,就像 typeof/instanceof 一样。我想知道为什么在 TypeScript 中需要它,因为你应该知道你的类型。 - user6445533
在 TypeScript 中,类型可以是 nullundefined,除非你将 strictNullChecks 设置为 true。 - str
显示剩余2条评论

8

用空对象重复OR运算

在编写引用可能存在或不存在的属性/子属性的HTML时,我无法离开Elvis。

我们不能写成:

this.myForm?.name?.first_name

我猜ES负责人可能大部分时间都在编写JS,而不是编写访问对象属性的前端HTML代码,因此他们对为什么需要安全导航感到困惑。

他们认为“没有人需要它”,“它允许错误保持沉默,可能会在以后造成问题”和“你如何解释 x?(y)?”

在等待未来的ES规范包含Elvis操作符的同时,我的解决方案如下:

(((this.myForm || {}).name || {}).first_name )

简而言之,在每个可能出现undefined的阶段,您都需要将其与{}进行OR运算,以便将未定义变为空对象。一旦您将整个内容放入括号中,就可以安全地尝试从中提取属性,因为您尝试从{}提取的任何属性都只是undefined,而不是实际错误。
当您有多个层级时,它确实会变得有点丑陋,但在许多情况下,它比使用一系列&&逐步遍历对象的级别要少丑陋。
对于数组访问,请将其与空数组[]进行OR运算。
如果要导航到一个数组,例如如果表单有很多名称并且您想选择第一个,则方法类似:
((((this.myForm || {}).names || [])[0] || {}).first_name )

如果链中的某个值为0或""会发生什么?

假设您希望从 this.myForm.names[0].first_name 读取值 "John"。

通常情况下,触发错误的典型情况是没有对象 this.myForm。但是,在我描述的公式中,不会出现错误,而会被解释为 {}.first_name,即 undefined(但不是错误)。

现在想象一下,一个敌对的人已经将 this.myForm 偷偷设置为0或""或{}。我们是否会看到错误?不会,因为Javascript认为值0或""为假,{}为真,但是本身就是{},所以 this.myForm || {} 求值为{},链的其余部分按照默认设置运行,该方法仍然起作用,返回 undefined 而不是错误。

示例

console.log("Each example shows first the result WITH the workaround, and then WITHOUT.")

console.log("a. This should work normally.")
a = { myForm:{ names:[ { first_name:"John", surname:"Smith"}]} };
console.log(a.myForm.names[0].first_name)
console.log ((((a.myForm || {}).names || [])[0] || {}).first_name )

console.log("b. We have removed the first_name property. This still works.")
b = { myForm:{ names:[ {                    surname:"Smith"}]} };
console.log(b.myForm.names[0].first_name)
console.log ((((b.myForm || {}).names || [])[0] || {}).first_name )

console.log("c. We have removed the entire 'names' array. This is fixed by the workaround, but gives an error if run without the workaround.")
c = { myForm:{ } };
console.log ((((c.myForm || {}).names || [])[0] || {}).first_name )
console.log(c.myForm.names[0].first_name)

    console.log("d. Now the whole root object is empty. Again the workaround gets around this, but without the workaround, we get an error.")
    d = { };
    console.log ((((d.myForm || {}).names || [])[0] || {}).first_name )
    console.log(d.myForm.names[0].first_name)


还没有尝试过,但看起来更加简洁。感谢分享。 如果您能提供一个工作示例,比如 Stackblitz 或 Plunker 等,那会更好。 - Pardeep Jain
3
注意:这个回复已经过时了,因为 TypeScript 3.7 引入了 ?. 运算符! - Didier68
但是我们普通的JavaScript程序员仍然可以使用它8-) - ProfDFrancis
如果你遇到一个非空值,它可以自动转换为布尔值,比如 "0","" 或者 [0],那么这将会产生难以发现的意外错误。 - Brixomatic
@Brixomatic。我试图在你的观点中进行编辑,但后来我意识到我找不到一个我的公式无法工作的情况。如果我尝试使用0或""或[0],该公式仍然可以正常工作而没有错误。你能给出一个反例吗? - ProfDFrancis
1
@Eureka 对不起,我错了,我改口。出乎意料的是,“0”== false,但是(“0”||“”)===“0”,尽管(false ||“”)===“”。这是一个很棘手的问题,会导致错误/意外的结论。 - Brixomatic

1
这是一个看起来有点冗长的解决方案:
const elvis = (...xs: (() => any|null)[]): any => {
    if(xs.length == 0) return null;
    const y = xs[0]();
    return xs.length == 1 || y == null ? y : elvis(...xs.slice(1));
};

const bla= {
    a : 'hello',
    b : null,
    c: undefined
}

console.log("a:", elvis(() => bla));
console.log("a:", elvis(() => bla, () => bla.a));
console.log("a:", elvis(() => bla, () => bla.a, () => bla.a.length));
console.log("b:", elvis(() => bla, () => bla.b, () => bla.b.length));
console.log("c:", elvis(() => bla, () => bla.c, () => bla.c.length));

输出:

> a: {a: "hello", b: null, c: undefined}
> a: hello
> a: 5
> b: null
> c: undefined

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