|
|
51CTO旗下网站
|
|
移动端

为什么我喜欢 JavaScript 可选链

很多 JavaScript 的特性极大地改变了你的编码方式。从 ES2015 及更高版本开始,对我的代码影响最大的功能是解构、箭头函数、类和模块系统。

作者:疯狂的技术宅来源:segmentfault|2019-10-23 15:53

很多 JavaScript 的特性极大地改变了你的编码方式。从 ES2015 及更高版本开始,对我的代码影响较大的功能是解构、箭头函数、类和模块系统。

截至2019年8月,一项新提案可选链(optional chaining)进入了第3阶段,将是一个很好的改进。可选的链接更改了从深层对象结构访问属性的方式。

让我们看看可选链是如何通过在深度访问可能缺少的属性时删除样板条件和变量来简化代码的。

1. 问题

由于 JavaScript 的动态特性,一个对象可以具有非常不同的对象嵌套结构。

通常,你可以在以下情况下处理此类对象:

  • 获取远程JSON数据
  • 使用配置对象
  • 具有可选属性

尽管这为对象提供了支持不同数据的灵活性,但是在访问此类对象的属性时,随之而来的是增加了复杂性。

bigObject 在运行时可以有不同的属性集:

  1. // One version of bigObject 
  2. const bigObject = { 
  3.   // ... 
  4.   prop1: { 
  5.     //... 
  6.     prop2: { 
  7.       // ... 
  8.       value: 'Some value' 
  9.     } 
  10.   } 
  11. }; 
  12.  
  13. // Other version of bigObject 
  14. const bigObject = { 
  15.   // ... 
  16.   prop1: { 
  17.     // Nothing here    
  18.   } 
  19. }; 

因此你必须手动检查属性是否存在:

  1. // Later 
  2. if (bigObject &&  
  3.     bigObject.prop1 != null &&  
  4.     bigObject.prop1.prop2 != null) { 
  5.   let result = bigObject.prop1.prop2.value; 

最好不要这样写,因为包含了太多的样板代码。。

让我们看看可选链是如何解决此问题,从而减少样板条件的。

2. 轻松深入访问属性

让我们设计一个保存电影信息的对象。该对象包含 title 必填属性,以及可选的 director 和 actor。

movieSmall 对象仅包含 title,而 movieFull 则包含完整的属性集:

  1. const movieSmall = { 
  2.   title: 'Heat' 
  3. }; 
  4.  
  5. const movieFull = { 
  6.   title: 'Blade Runner'
  7.   director: { name'Ridley Scott' }, 
  8.   actors: [{ name'Harrison Ford' }, { name'Rutger Hauer' }] 
  9. }; 

让我们写一个获取导演姓名的函数。请注意 director 属性可能会丢失:

  1. function getDirector(movie) { 
  2.   if (movie.director != null) { 
  3.     return movie.director.name
  4.   } 
  5.  
  6. getDirector(movieSmall); // => undefined 
  7. getDirector(movieFull);  // => 'Ridley Scott' 

if (movie.director) {...} 条件用于验证是否定义了 director 属性。如果没有这种预防措施,则在访问movieSmall 对象的导演的时,JavaScript 会引发错误 TypeError: Cannot read property 'name' of undefined。

这是用了可选链功能并删除 movie.director 存在验证的正确位置。新版本的 getDirector() 看起来要短得多:

  1. function getDirector(movie) { 
  2.   return movie.director?.name
  3.  
  4. getDirector(movieSmall); // => undefined 
  5. getDirector(movieFull);  // => 'Ridley Scott' 

在 movie.director?.name 表达式中,你可以找到 ?.:可选链运算符。

对于 movieSmall,缺少属性 director。结果 movie.director?.name 的计算结果为 undefined。可选链运算符可防止引发 TypeError: Cannot read property 'name' of undefined 错误。

相反 movieFull 的属性 director是可用的。 movie.director?.name 默认被评估为 'Ridley Scott'。

简而言之,代码片段:

  1. let name = movie.director?.name

等效于:

  1. let name
  2. if (movie.director != null) { 
  3.   name = movie.director.name

?. 通过减少两行代码简化了 getDirector() 函数。这就是为什么我喜欢可选链的原因。

2.1 数组项

可选链能还可以做更多的事。你可以在同一表达式中自由使用多个可选链运算符。甚至可以用它安全地访问数组项!

下一个任务编写一个返回电影主角姓名的函数。

在电影对象内部,actor 数组可以为空甚至丢失,所以你必须添加其他条件:

  1. function getLeadingActor(movie) { 
  2.   if (movie.actors && movie.actors.length > 0) { 
  3.     return movie.actors[0].name
  4.   } 
  5.  
  6. getLeadingActor(movieSmall); // => undefined 
  7. getLeadingActor(movieFull);  // => 'Harrison Ford' 

如果需要 if (movie.actors && movies.actors.length > 0) {...} ,则必须确保 movie 包含 actors 属性,并且该属性中至少有一个 actor。

使用可选链,这个任务就很容易解决:

  1. function getLeadingActor(movie) { 
  2.   return movie.actors?.[0]?.name
  3.  
  4. getLeadingActor(movieSmall); // => undefined 
  5. getLeadingActor(movieFull);  // => 'Harrison Ford' 

actors?. 确保 actors 属性存在。 [0]?. 确保列表中存在第一个参与者。这真是个好东西!

3. 默认为Nullish合并

一项名为nullish 合并运算符的新提案会处理 undefined 或 null ,将其默认设置为特定值。

如果 variable 是 undefined 或 null,则表达式 variable ?? defaultValue 的结果为 defaultValue。否则,表达式的计算结果为 variable 值。

  1. const noValue = undefined; 
  2. const value = 'Hello'
  3.  
  4. noValue ?? 'Nothing'; // => 'Nothing' 
  5. value   ?? 'Nothing'; // => 'Hello' 

当链评估为 undefined 时,通过将默认值设置为零,Nullish 合并可以改善可选链。

例如,让我们更改 getLeading() 函数,以在电影对象中没有演员时返回 "Unknown actor" :

  1. function getLeadingActor(movie) { 
  2.   return movie.actors?.[0]?.name ?? 'Unknown actor'
  3.  
  4. getLeadingActor(movieSmall); // => 'Unknown actor' 
  5. getLeadingActor(movieFull);  // => 'Harrison Ford' 

4. 可选链的3种形式

你可以通过以下 3 种形式使用可选链。

第一种形式的 object.property 用于访问静态属性:

  1. const object = null
  2. object?.property; // => undefined 

第二种形式 object?.[expression] 用于访问动态属性或数组项:

  1. const object = null
  2. const name = 'property'
  3. object?.[name]; // => undefined 
  1. const array = null
  2. array?.[0]; // => undefined 

最后,第三种形式 object?.([arg1, [arg2, ...]]) 执行一个对象方法:

  1. const object = null
  2. object?.method('Some value'); // => undefined 

如果需要,可以将这些形式组合起来以创建长的可选链:

  1. const value = object.maybeUndefinedProp?.maybeNull()?.[propName]; 

5.短路:在null/undefined 处停止

可选链运算符的有趣之处在于,一旦在其左侧 leftHandSide?.rightHandSide 上遇到空值,就会停止对右侧访问器的评估。这称为短路。

看一个例子:

  1. const nothing = null
  2. let index = 0; 
  3.  
  4. nothing?.[index++]; // => undefined 
  5. index;              // => 0 

nothing 保留一个空值,因此可选链立即求值为 undefined,并跳过右侧访问器的求值。因为 index 的值没有增加。

6. 何时使用可选链

要抵制使用可选链运算符访问任何类型属性的冲动:这会导致错误的用法。下一节将说明何时正确使用它。

6.1 可能无效的访问属性

必须仅在可能为空的属性附近使用 ?.: maybeNullish?.prop。在其他情况下,请使用老式的属性访问器:.property 或 [propExpression]。

调用电影对象。查看表达式 movie.director?.name,因为 director 可以是 undefined,所以在 director 属性附近使用可选链运算符是正确的。

相反,使用 ?. 访问电影标题 movie?.title 没有任何意义。电影对象不会是空的。

  1. // Good 
  2. function logMovie(movie) { 
  3.   console.log(movie.director?.name); 
  4.   console.log(movie.title); 
  5.  
  6. // Bad 
  7. function logMovie(movie) { 
  8.   // director needs optional chaining 
  9.   console.log(movie.director.name); 
  10.  
  11.   // movie doesn't need optional chaining 
  12.   console.log(movie?.title); 

6.2 通常有更好的选择

以下函数 hasPadding() 接受具有可选 padding 属性的样式对象。 padding 具有可选的属性 left,top,right,bottom。

尝试用可选链运算符:

  1. function hasPadding({ padding }) { 
  2.   const top = padding?.top ?? 0; 
  3.   const right = padding?.right ?? 0; 
  4.   const bottom = padding?.bottom ?? 0; 
  5.   const left = padding?.left ?? 0; 
  6.   return left + top + right + bottom !== 0; 
  7.  
  8. hasPadding({ color: 'black' });        // => false 
  9. hasPadding({ padding: { left: 0 } });  // => false 
  10. hasPadding({ padding: { right: 10 }}); // => true 

虽然函数可以正确地确定元素是否具有填充,但是为每个属性使用可选链是毫无必要的。

更好的方法是使用对象散布运算符将填充对象默认为零值:

  1. function hasPadding({ padding }) { 
  2.   const p = { 
  3.     top: 0, 
  4.     right: 0, 
  5.     bottom: 0, 
  6.     left: 0, 
  7.     ...padding 
  8.   }; 
  9.   return p.top + p.left + p.right + p.bottom !== 0; 
  10.  
  11. hasPadding({ color: 'black' });        // => false 
  12. hasPadding({ padding: { left: 0 } });  // => false 
  13. hasPadding({ padding: { right: 10 }}); // => true 

我认为这一版本的 hasPadding() 可读性更好。

7. 我为什么喜欢它?

我喜欢可选链运算符,因为它允许轻松地从嵌套对象中访问属性。它可以防止编写针对访问者链中每个属性访问器上的空值进行验证的样板代码。

当可选链与空值合并运算符结合使用时,可以得到更好的结果,从而更轻松地处理默认值。

【编辑推荐】

  1. 揭开JavaScript引擎的面纱
  2. 5个JavaScript不良编码习惯,你占几个呢?
  3. 这篇文章,让你了解 JavaScript 中的原型(基础篇-图文)
  4. 五门最有前景的编程语言,JavaScript居然垫底
  5. 2019年流行的JavaScript框架
【责任编辑:华轩 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

中间件安全防护攻略

中间件安全防护攻略

4类安全防护
共4章 | hack_man

111人订阅学习

CentOS 8 全新学习术

CentOS 8 全新学习术

CentOS 8 正式发布
共16章 | UbuntuServer

274人订阅学习

用Python玩转excel

用Python玩转excel

让重复操作傻瓜化
共3章 | DE8UG

231人订阅学习

读 书 +更多

非常网管——网络应用

在网络应用越来越复杂的今天,传统的网络应用已经不能满足企业和用户的需要,这就对网络管理员、信息管理部门提出了更高的要求。本书介绍了...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微