ES6 / 2015中的空安全属性访问(和条件赋值)


149

是否有一个null-safe属性访问(空传播/存在)操作者在ES6(ES2015 / JavaScript.next /和谐)等?.的CoffeeScript例如?还是计划针对ES7?

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

大致如下所示:

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

理想情况下,解决方案不应该分配(甚至undefined)到aThing如果possiblyNullISnull


3
@naomik对于您正在检查深度嵌套属性的if语句,例如,if( obj?.nested?.property?.value )而不是if( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh

@SeanWalsh如果您的对象被深深地嵌套,或者您的函数正在对象中进行深深的挖掘,则应用程序可能还会存在其他一些问题。
谢谢您

1
比较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。参考:得墨meter耳定律
谢谢您

另外,如果您进行设置默认值或清除属性的操作(可以在loadConfig上面的示例中完成),则可以对属性的存在进行假设,并在应用程序的无数区域中跳过空检查。
谢谢您

4
@naomik只要该语言支持嵌套对象,它仍然是一个有用的功能-无论您或我对应用程序本身的体系结构有何看法。顺便说一句,像这样的复杂对象图在建模复杂数据模型的ORM中非常常见。
肖恩·沃尔​​什

Answers:


97

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

更新(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
ᆼᆺᆼ

1
其实很不幸,我认为您是对的。Street 我认为会被分配undefined但是至少它不会尝试访问未定义的属性。
basicdays

2
看起来您也可以用左手操作符,如果评估为未定义,则不会评估右手。babel插件可能会有所不同,我自己还没有对其进行太多测试。
basicdays

1
关于左侧的条件赋值,=目前官方规范中似乎不支持该条件赋值。github.com/tc39/proposal-optional-chaining#not-supported
basicdays '19

1
截至2020年5月,看起来当前的浏览器和Typescript已实现此功能!
乔什·代尔

65

它不如?好。运算符,但是要达到类似的结果,您可以执行以下操作:

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

由于nullundefined都是伪造的值(请参阅此参考资料),&&只有在先例不是null或未定义的情况下,才可以访问运算符之后的属性。

另外,您可以编写如下函数:

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) :)
rob2d

foo && foo.bar && foo.bar.quux ...在大型代码库中,这很丑陋,并且增加了很多复杂性,您最好避免这种情况。
Skylar Saveland

1
这是我在互联网上找到的最干净的解决方案。我将其与打字稿一起使用:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing

3
如果user.address.postcode未定义,_try(() => user.address.postcode, "")将返回undefined而不是""。因此,代码_try(() => user.address.postcode, "").length将引发异常。
亚历山大·陈

33

否。您可以在JavaScript中使用lodash#get或类似的方法。


4
是否有建议将其添加到ES7?
2015年

1
没遇到过。
吉拉法

3
@PeterVarga:很多。讨论过多。语法是不是该功能件容易的事
BERGI

1
lodash.get略有不同,因为它显然不能进行条件赋值。
2015年

17

香草替代品,可安全进入物业

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

最简洁的条件分配可能是这样

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

那么,您将如何使用这种机制将分配写在问题中?如果possiblyNull未定义/,您还不需要条件不分配null吗?
ᆼᆺᆼ

当然,您仍然需要检查是否要执行任务。如果使用'='运算符,将不可避免地分配一些内容,无论是数据,未定义还是null。因此,以上只是安全的属性访问。条件赋值运算符甚至以任何语言存在吗?
yagger

当然,例如CoffeeScript,它也具有||=
'15:52

+1甚至我的第一个念头就是(((a.b || {}).c || {}).d || {}).e。但更精确的是((((a || {}).b || {}).c || {}).d || {}).e
IsmailS

6

不,ES6中没有空传播算子。您将不得不采用一种已知模式

您也许可以使用解构:

({thing: aThing} = possiblyNull);

有很多讨论(例如this)在ES7中添加这样的运算符,但没有一个真正起步。


看起来很有希望,但是至少Babel所做的和它没有什么不同aThing = possiblyNull.thing
2015年

1
@PeterVarga:哎呀,是的,当属性不存在时解构有效,但当对象为时就不可行null。您必须提供一个默认值,非常类似于此模式,但语法更加混乱。
Bergi

4

按照此处的列表进行操作,当前没有提议向Ecmascript添加安全遍历。因此,不仅没有很好的方法可以做到这一点,而且在可预见的将来也不会添加它。


1
// 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);

1

可选链接?.和空位合并??

现在,您可以直接使用?.内联安全地测试存在性。所有现代的浏览器都支持它。

?? 如果未定义或为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

如果不检查情况,则必须存在left-side属性。如果没有,它将抛出异常。

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

?.浏览器支持 -2020年7月占78%

??浏览器支持 -78%

Mozilla文档

-

逻辑无效分配,2020 +解决方案

新的运营商目前正在被添加到浏览器??=||=&&=。它们不能完全满足您的要求,但根据您的代码目标,可能会导致相同的结果。

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

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

基本范例

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" }

浏览器支持 2020年7月-.03%

Mozilla文档


1
问题也与条件分配有关
ᆼᆺᆼ

使用?.和添加了一些示例,以及??即将面世的详细解决方案,可能会适合您的情况。当前最好的解决方案可能就是这样做aThing = possiblyNull?.thing ?? aThing
Gibolt

0

一个安全的深度获取方法似乎很自然地适合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)来解释:这听起来像是用额外的步骤进行字符串编程。
tocqueville

1
lodash现在实现了深层获取/设置 _.get(obj, array_or_dotstring)
原型

1
但是公平地说,即使Javascript点和字符串访问器表示法基本上也是字符串编程,obj.a.b.cobj['a']['b']['c']
原型是

1
哪里keys来的?
列维·罗伯茨

-2

我知道这是一个JavaScript问题,但是我认为Ruby以所有要求的方式处理此问题,因此我认为这是一个相关的参考点。

.&,,try和&&具有优势和潜在的陷阱。这些选项的摘要如下:http : //mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR;Rubyists的结论是,dig既易于使用,又可以更有力地保证null将被赋值。

这是TypeScript中的一个简单实现:

export function dig(target: any, ...keys: Array<string>): any {
  let digged = target
  for (const key of keys) {
    if (typeof digged === 'undefined') {
      return undefined // can also return null or a default value
    }
    if (typeof key === 'function') {
      digged = key(digged)
    } else {
      digged = digged[key]
    }
  }
  return digged
}

这可用于任何深度的嵌套和句柄功能。

a = dig(b, 'c', 'd', 'e');
foo = () => ({});
bar = dig(a, foo, 'b', 'c')

try前面的答案所示,该方法同样适合在JS中阅读。它还不需要循环,这是此实现方式的缺点之一。


我在这里把这个答案作为一个有趣/完整/替代性解决方案b / c,我喜欢红宝石用语法糖做到这一点的方式。只需将其发布给社区即可-再次投票,我将很乐意删除该帖子。干杯!
theUtherSide

真正冷静地看到ECMAScript2019有红宝石般的“可选链接”一个伟大的提案:github.com/tc39/proposal-optional-chaining 今天是通过提供通天插件,和它的作品调用对象太上的方法:babeljs.io / docs / zh-
CN

-5

我认为这个问题需要在2018年进行一些刷新。可以在不使用任何库的情况下很好地完成此操作,Object.defineProperty()并且可以按以下方式使用它:

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

我认为这是安全的(和js道德的),因为MDN中记录了的方法的writeableenumerable定义,definePropertyObject

函数定义如下:

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版本中,我还为空值添加了一个自定义异常。这是可选的,因此我将其排除在上面的最小定义之外。

欢迎改进


2
这样,您实际上是在将代码编写为字符串。这样做确实是一个坏主意。它使您的代码无法重构,并且不兼容IDE。
tocqueville '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.