【问题标题】:Replacement of Elvis Operator of Angular2 in Typescript在 Typescript 中替换 Angular2 的 Elvis 运算符
【发布时间】:2017-01-07 21:52:34
【问题描述】:

打字稿中是否有与 angular2 的Elvis Operator 类似的运算符,我的意思是说 假设我必须从这样的对象中获取密钥:

this.myForm.name.first_name

如果 first_name 不存在,则会抛出错误first_name of undefined

是的,我可以像这样使用打字稿的Ternary operator 来处理这个错误

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

但有时键会变得太长,

打字稿中是否有像Elvis Operator of angular2 这样的运算符,以便我可以这样使用

this.myForm?.name?.first_name

【问题讨论】:

标签: javascript angular typescript


【解决方案1】:

用空对象反复 OR

在编写引用可能存在或不存在的属性/子属性的 HTML 时,我不能没有 Elvis。

我们不能写

this.myForm?.name?.first_name

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

他们争辩说“没有人需要它”、“它可以让错误保持沉默,从而可能会在以后咬你”和“你会如何解释x?(y)?”

当我等待未来的 ES 规范包含 Elvis 运算符时,我的解决方案如下:

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

简而言之,在值可能为undefined 的每个阶段,您将OR{} 结合起来,这样未定义的对象就变成了一个空对象。一旦将整个内容放入括号中,您就可以安全地尝试提取它的属性,因为您尝试从 {} 提取的任何属性都只是 undefined 而不是实际错误。

当您有多个层时,它确实会有点难看,但在许多情况下,它比必须使用一系列 && 来逐步降低对象的层级更难看。

对于数组访问,或它与一个空数组

如果导航到一个数组中,例如,如果表单有很多名称并且您想选择第一个,方法类似:

((((this.myForm || {}).names || [])[0] || {}).first_name )

如果链中的某些东西是 0 或 "" 会发生什么?

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

通常会触发错误的典型情况是根本没有对象this.myForm。我上面描述的公式不会出错,而是可以工作,并被解释为{}.first_name,即undefined(但不是错误)。

现在想象一个敌对的人偷偷地将 this.myForm 设置为 0 或 "" 或 {}。我们现在会看到错误吗?不,因为值 0 或 "" 被 Javascript 认为是虚假的,而 {} 是真实的,但它本身就是 {},所以 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 等。
  • 注意:此响应不再是最新的,因为 ?. 同时已被 TypeScript 3.7 引入!
  • 但是我们普通的 Javascripters 仍然可以使用它 8-)
  • 如果您遇到可自动转换为布尔值的非空值(如“0”、“”或 [0]),则会产生难以发现的意外错误。跨度>
  • @Eureka 我的错,我已经纠正了。出乎意料的一点是“0” == false,但是 ("0" || "") === "0",虽然 (false || "") === ""。这是一个滑坡,会导致错误/意外的结论。
【解决方案2】:

这是一个看起来更冗长的解决方案:

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

【讨论】:

    【解决方案3】:

    2019 年 12 月更新:TypeScript 3.7 introduced Optional Chaining 相当于其他语言中已知的安全导航运算符。 ECMAScript 提案optional chaining 已进入第 4 阶段,因此将成为 ES2020 规范的一部分。请参阅mdn: Optional chaining 了解更多信息。


    2017 年 7 月更新:正如 JGFMK 在 cmets 中指出的,有一个名为 Optional Chaining for JavaScript 的 ECMAScript 提案。如果/当提案达到第 4 阶段时,它将被添加到语言规范中。


    TypeScript 中既没有安全导航也没有 elvis 运算符,据我所知,也没有可比性。

    有关参考,请参阅功能请求 Suggestion: "safe navigation operator", i.e. x?.yexplanation for not implementing it 如下(在我看来,这是一个正当的理由):

    暂时关闭。由于实际上没有任何特定于 TypeScript 的东西需要在表达式级别进行此操作,因此这种大的运算符更改应该发生在 ES 规范委员会而不是这里。

    重新评估此功能的一般绊线将是到达下一阶段的具体 ES 提案,或者 ES 委员会的普遍共识,即该功能不会在很长一段时间内发生(以便我们可以定义自己的语义并有理由确定他们会“赢”)。

    该表示法的替代方法是使用逻辑 AND 运算符、try/catch 或 getSafe(() => this.myForm.name.first_name) 等辅助函数,如 this post 中所述。

    【讨论】:

    • 感谢您的回答,除了三元运算符还有其他替代方法吗?
    • @PardeepJain 你可以做的是在某些属性未定义时分配空对象:this.myForm.name = this.myForm.name || {}。然后您可以确定name 已定义。另一种方法是定义一个默认数据结构,然后使用Object.assign 用您的实际数据覆盖它。
    • 有这个github.com/tc39/proposal-optional-chaining 还在提案阶段。没有意识到这是一个 Angular 调整...假设它已被写入 Typescript...
    • 鸭子类型只是对象类型的运行时类型检查的一种形式,就像typeof/instanceof一样。我想知道为什么在 TypeScript 中它是必要的,因为你应该知道你的类型。
    • @ftor TypeScript 中的类型可以是nullundefined,除非您将strictNullChecks 设置为true。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-11
    • 1970-01-01
    • 2016-05-05
    • 2019-02-16
    • 2018-04-11
    相关资源
    最近更新 更多