【问题标题】:Null-safe property access (and conditional assignment) in ES6/2015ES6/2015 中的空安全属性访问(和条件赋值)
【发布时间】:2015-11-15 07:48:26
【问题描述】:

在 ES6 (ES2015/JavaScript.next/Harmony) 中是否有 null-safe 属性访问(空传播/存在)运算符,例如 CoffeeScript 中的 ?. > 例如?还是计划在 ES7 上使用?

var aThing = getSomething()
...
aThing = possiblyNull?.thing

大致如下:

if (possiblyNull != null) aThing = possiblyNull.thing

理想情况下,如果possiblyNullnull,则解决方案不应分配(甚至undefined)给aThing

【问题讨论】:

  • @naomik 这种空值检查对于检查深层嵌套属性的 if 语句非常有用,例如if( obj?.nested?.property?.value ) 而不是 if( obj && obj.nested && obj.nested.property && obj.nested.property.value )
  • @SeanWalsh 如果您的对象嵌套得那么深,或者您的函数在对象中挖掘得那么深,那么您的应用程序可能还有其他几个问题。
  • 比较 var appConfig = loadConfig(config, process.env); connect(appConfig.database);connect(config)。您可以将一个更简单的对象传递给connect,而不是传递整个config 对象,您可以使用conf.usernameconf.password 而不是尝试像config[process.env]?.database?.usernameconfig[process.env]?.database?.password 这样的东西。参考:Law of Demeter.
  • 此外,如果您执行设置默认值或清理属性等操作(这可以在上面示例中的loadConfig 中完成),您可以假设属性的存在并跳过无数次的 null 检查应用的各个区域。
  • @naomik 只要语言支持嵌套对象,它仍然是一个有用的功能——不管你或我如何看待应用程序本身的架构。顺便说一句,像这样的复杂对象图在对复杂数据模型进行建模的 ORM 中非常常见。

标签: javascript coffeescript ecmascript-6 babeljs


【解决方案1】:

没有。你可以在 JavaScript 中使用 lodash#get 或类似的东西。

【讨论】:

  • 是否有建议将其添加到 ES7 中?
  • 没遇到过。
  • @PeterVarga:很多。太多的讨论。语法对于这个功能来说不是一件容易的事
  • lodash.get 略有不同,显然它不能进行条件赋值。
【解决方案2】:

不,ES6 中没有空传播运算符。您必须选择known patterns 之一。

不过,您也许可以使用解构:

({thing: aThing} = possiblyNull);

有很多讨论(例如this)在 ES7 中添加这样的运算符,但直到几年后 optional chaining syntax 在 ES2020 中标准化时才真正起飞。

【讨论】:

  • 这看起来很有希望,但至少 Babel 用它做的事情与 aThing = possiblyNull.thing 没有任何不同
  • @PeterVarga:哎呀,你是对的,当属性不存在时解构有效,但当对象为null 时无效。你必须提供一个默认值,很像this pattern,但语法更混乱。
  • 这个答案已经过时了 - 大约 10 个月前,空安全运算符被正式添加到 ES6 中,并且大多数浏览器支持它大约一年前才正式发布。
  • @ArtOfWarfare 它们不是添加到 ES6(ES2015)而是添加到 ES2020,所以答案实际上仍然是正确的并且回答了仅询问 ES6 的问题。无论如何更新:-)
【解决方案3】:

安全访问的香草替代品

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

最简洁的条件赋值可能是这个

try { b = a.b.c.d.e } catch(e) {}

【讨论】:

  • 那么您将如何使用这种机制在问题中编写作业?如果possiblyNull 未定义/null,您是否还需要条件不分配?
  • 当然,您仍然需要检查是否要进行分配。如果使用 '=' 运算符,将不可避免地分配某些内容,无论是数据、未定义还是 null。所以以上只是一个安全的财产访问。条件赋值运算符是否存在,在任何语言中?
  • 当然例如CoffeeScript,它也有||=
  • +1 甚至我的第一个想法也是(((a.b || {}).c || {}).d || {}).e。但更准确地说是((((a || {}).b || {}).c || {}).d || {}).e
【解决方案4】:

按照here 列表,目前没有向 Ecmascript 添加安全遍历的提议。所以不仅没有很好的方法可以做到这一点,而且在可预见的将来也不会添加。

编辑:由于我最初发表这篇文章,它实际上是添加到语言中的。

【讨论】:

    【解决方案5】:

    它不如 ?。运算符,但要获得类似的结果,您可以这样做:

    user && user.address && user.address.postcode
    

    由于 nullundefined 都是 falsy 值 (see this reference),因此仅当先例不为 null 或未定义时才会访问 && 运算符之后的属性。 p>

    或者,您可以编写如下函数:

    function _try(func, fallbackValue) {
        try {
            var value = func();
            return (value === null || value === undefined) ? fallbackValue : value;
        } catch (e) {
            return fallbackValue;
        }
    }
    

    用法:

    _try(() => user.address.postcode) // return postcode or undefined 
    

    或者,使用后备值:

    _try(() => user.address.postcode, "none") // return postcode or a custom string
    

    【讨论】:

    • 好吧,也许我应该澄清一下这个问题,我主要追求的是条件赋值
    • 只是一个必然结果;要安全地找到逆,您可以使用!(user && user.address && user.address.postcode) :)
    • foo && foo.bar && foo.bar.quux ... 在大型代码库中,这很丑陋,并且会增加很多复杂性,最好避免。
    • 这是我在互联网上找到的最干净的解决方案。我将它与打字稿一起使用:_get<T>(func: () => T, fallbackValue?: T) :T
    • 如果 user.address.postcode 未定义,_try(() => user.address.postcode, "") 将返回 undefined 而不是 ""。所以代码_try(() => user.address.postcode, "").length 会引发异常。
    【解决方案6】:

    更新(2022-01-13):似乎人们仍在寻找这个,这是当前的故事:

    更新 (2017-08-01):如果你想使用官方插件,你可以尝试使用新转换的 Babel 7 的 alpha 版本。 您的里程可能会有所不同

    https://www.npmjs.com/package/babel-plugin-transform-optional-chaining

    原创

    目前处于第 1 阶段的功能:可选链。

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

    如果你今天想使用它,有一个 Babel 插件可以做到这一点。

    https://github.com/davidyaha/ecmascript-optionals-proposal

    【讨论】:

    • 我是否正确理解这不包括条件赋值? street = user.address?.street 无论如何都会设置street
    • 其实很遗憾我认为你是对的。 Street 我认为会被分配undefined但是至少它不会尝试访问未定义的属性。
    • 看起来您也可以使用左侧的运算符,如果它评估任何未定义的内容,则不会评估右手。 babel 插件可能会有所不同,我自己还没有测试过。
    • 关于=左侧的条件赋值,目前官方规范似乎不支持。 github.com/tc39/proposal-optional-chaining#not-supported
    • 截至 2020 年 5 月,目前的浏览器和 Typescript 似乎已经实现了这一点!
    【解决方案7】:

    我认为这个问题需要在 2018 年更新一下。这可以在没有任何库的情况下使用 Object.defineProperty() 很好地完成,并且可以按如下方式使用:

    myVariable.safeGet('propA.propB.propC');
    

    我认为这是安全的(和 js-ethical),因为 writeableenumerable 定义现在可用于 ObjectdefineProperty 方法,如 documented in MDN

    函数定义如下:

    Object.defineProperty(Object.prototype, 'safeGet', { 
        enumerable: false,
        writable: false,
        value: function(p) {
            return p.split('.').reduce((acc, k) => {
                if (acc && k in acc) return acc[k];
                return undefined;
            }, this);
        }
    });
    

    我已经将jsBin 与控制台输出放在一起来演示这一点。请注意,在 jsBin 版本中,我还为空值添加了自定义异常。这是可选的,所以我把它排除在上面的最小定义之外。

    欢迎改进

    【讨论】:

    • 有了这个,您事实上将代码写入字符串。这样做真的是个坏主意。它使您的代码无法重构并且对 IDE 不友好。
    【解决方案8】:

    安全的深度获取方法似乎很适合 underscore.js,但问题在于避免字符串编程。修改@Felipe 的答案以避免字符串编程(或至少将边缘情况推回给调用者):

    function safeGet(obj, props) {
       return (props.length==1) ? obj[keys[0]] :safeGet(obj[props[0]], props.slice(1))
    }
    

    例子:

    var test = { 
      a: { 
        b: 'b property value',
        c: { }
      } 
    }
    safeGet(test, ['a', 'b']) 
    safeGet(test, "a.b".split('.'))  
    

    【讨论】:

    • 套用 Rick 的话说:这听起来像是带有额外步骤的字符串编程。
    • lodash 现在实现了深度获取/设置_.get(obj, array_or_dotstring)
    • 但公平地说,即使是 Javascript 点和字符串访问器符号基本上也是字符串编程,obj.a.b.c vs obj['a']['b']['c']
    • keys 来自哪里?
    【解决方案9】:
    // Typescript
    static nullsafe<T, R>(instance: T, func: (T) => R): R {
        return func(instance)
    }
    
    // Javascript
    function nullsafe(instance, func) {
        return func(instance);
    };
    
    // use like this
    const instance = getSomething();
    let thing = nullsafe(instance, t => t.thing0.thing1.thingx);
    

    【讨论】:

      【解决方案10】:

      2020 解决方案,?.??

      您现在可以直接使用?.(可选链接)内联来安全地测试是否存在。所有现代浏览器都支持它。

      ??(Nullish Coalescing)可用于在未定义或 null 时设置默认值。

      aThing = possiblyNull ?? aThing
      aThing = a?.b?.c ?? possiblyNullFallback ?? aThing
      

      如果属性存在,?. 会进行下一次检查,或者返回有效值。任何故障都会立即短路并返回undefined

      const example = {a: ["first", {b:3}, false]}
      
      example?.a  // ["first", {b:3}, false]
      example?.b  // undefined
      
      example?.a?.[0]     // "first"
      example?.a?.[1]?.a  // undefined
      example?.a?.[1]?.b  // 3
      
      domElement?.parentElement?.children?.[3]?.nextElementSibling
      
      null?.()                // undefined
      validFunction?.()       // result
      (() => {return 1})?.()  // 1
      

      为确保默认定义值,您可以使用??。如果需要第一个真值,可以使用||

      example?.c ?? "c"  // "c"
      example?.c || "c"  // "c"
      
      example?.a?.[2] ?? 2  // false
      example?.a?.[2] || 2  // 2
      

      如果不检查大小写,则左侧属性必须存在。如果没有,它会抛出异常。

      example?.First         // undefined
      example?.First.Second  // Uncaught TypeError: Cannot read property 'Second' of undefined
      

      ?. Browser Support - 92%,2021 年 11 月

      ?? Browser Support - 92%

      Mozilla Documentation

      --

      逻辑无效赋值,2020+ 解决方案

      新的运算符目前正在添加到浏览器中,??=||=&amp;&amp;=。它们并不能完全满足您的需求,但可能会导致相同的结果,具体取决于您的代码目标。

      注意:这些在公共浏览器版本中并不常见,但 Babel 应该可以很好地转换。将随着可用性的变化而更新。

      ??= 检查左侧是否未定义或为空,如果已定义则短路。如果不是,则为左侧分配右侧值。 ||=&amp;&amp;= 相似,但基于 ||&amp;&amp; 运算符。

      基本示例

      let a          // undefined
      let b = null
      let c = false
      
      a ??= true  // true
      b ??= true  // true
      c ??= true  // false
      

      对象/数组示例

      let x = ["foo"]
      let y = { foo: "fizz" }
      
      x[0] ??= "bar"  // "foo"
      x[1] ??= "bar"  // "bar"
      
      y.foo ??= "buzz"  // "fizz"
      y.bar ??= "buzz"  // "buzz"
      
      x  // Array [ "foo", "bar" ]
      y  // Object { foo: "fizz", bar: "buzz" }
      

      Browser Support 2021 年 11 月 - 90%

      Mozilla Documentation

      【讨论】:

      • 问题也是关于条件赋值
      • 添加了一些带有?.?? 的示例,以及即将推出的可能适合您情况的详细解决方案。当前最好的解决方案可能是只做aThing = possiblyNull?.thing ?? aThing
      猜你喜欢
      • 2018-06-16
      • 2018-10-03
      • 2016-04-03
      • 1970-01-01
      • 2019-08-14
      • 2021-06-24
      • 2021-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多