解构赋值默认值误区

综合技术 2018-05-18 阅读原文

问题还原

这是最近 CR 的时候在业务代码中发现了一个问题,先来看一下问题代码:

// data 为接口返回的数据
const { bizObject = {}, total = 0 } = data.result || {};
const list = bizObject.list || [];

// 其他逻辑,比如把 list 更新到 state 中,等等
  • A 接口正常的情况: data
    中有 result
    属性,且 result
    对象中 bizObject
    返回了一个数—— :white_check_mark:
  • B 接口异常: data
    对象中没有 result
    属性 —— :white_check_mark:
  • C 接口异常: data
    中有 result
    属性, result
    对象中也有 bizObject
    属性,但是, bizObject
    属性的值是 null
    ,然后呢?

从上下文来看,这位同学应该是期望解构赋值按以下方式执行:

const result = data.result || {};
const bizObject = result.bizObject || {};
// ...

但是,C 情形抛异常了:

Uncaught TypeError: Cannot read property 'list' of null

也就是 bizObject
的值是 null
而不是期望的 {}
。为什么呢?

解构赋值中的默认值

A variable can be assigned a default, in the case that the value unpacked from the object/array is undefined
.

数组、对象解构赋值时,只有当属性(数组索引对应的值)值为 undefined
时,才会使用默认值。

问题代码中,当 bizObject
null
时,解构出来的就是 null
,读取 null
list
属性,不报错才怪。

函数默认参数

再来看看函数的默认参数是不是同样的逻辑:

function doSomething(options = { foo: 'bar' }) {
  console.log(options);
}

doSomething(); // { foo: "bar" }
doSomething(undefined); // { foo: "bar" }
doSomething(null); // null

不传参数(隐式的 undefined
)或者显示地传递 undefined
,使用了默认参数,传 null
的时候没有使用默认值,和解构赋值的默认值同样的逻辑。

其实把上面的函数转成 ES5,就能直观地了解其逻辑:

function doSomething() {
  var options =
    arguments.length > 0 && arguments[0] !== undefined
      ? arguments[0]
      : {
          foo: "bar"
        };
  console.log(options);
}

解构赋值的默认值也一样:

// ES6
const { a = 1 } = { };

// 转换成 ES5
var _ref = {},
  _ref$a = _ref.a,
  a = _ref$a === void 0 ? 1 : _ref$a;

写在最后

之所以会有同学把解构赋值默认值等同于 const bizObject = result.bizObject || {}
,可能是对 ES6 的一些细节了解得不够透彻,可以多翻翻文档:

还有一个可能,前端同学并没有误解解构赋值默认值的工作原理,只是接口不规范引发了异常。一般而言,
接口约定好字段、类型后,就应该始终按约定的类型返回数据,约定的是对象,那没有数据的时候也应该返回一个空对象,即使不返回这个字段,前端也已经判断了,莫名其妙地返回一个 null
算哪门子事

接口写得有问题,有的人沟通一下还是会调整,有的人就始终一副「放荡不羁」样子,通了就行,才不管你什么规范、约定……对于不讲究的人,还是自己多写两行代码判断一下,说多了也是浪费,你懂的。

很多事情都是 100% 的期望,然后妥协,接受一个差不多的结果。(BGM 差不多先生 - MC Hot Dog

责编内容by:CSSPod 【阅读原文】。感谢您的支持!

您可能感兴趣的

Lazy deserialization TL;DR: Lazy deserialization was recently enabled by default inV8 version 6.4, reducing V8’s memory consumption by over 500 KB per browser tab on aver...
egg-wechat-api@1.2.0 Wechat/Dingtalk 一类的 SDK 对 accessToken/JsTicketToken 有访问频率限制。 eggjs-community/egg-wechat-api 微信 SDK 初始化: async function getAccessToken() { ...
Fresh Resources for Web Developers – July 2017 Time flies and we are now in the middle of 2017. In this month’s installment, we have an array of great JavaScript libraries and a few others like a p...
web性能优化——关键渲染路径(上) 从收到 HTML、CSS 和 JavaScript 字节到对其进行必需的处理,从而将它们转变成渲染的像素这一过程中有一些中间步骤,优化性能其实就是了解这些步骤中发生了什么, 即 关键渲染路径 。 优化关键渲染路径 是指优先显示与当前用户操作有关的内容。 通过优化关键渲染路径,我...
Accordion of closing parents must close all accord... I'm using bootstrap accordion and I make it to work with a nested accordion. What I'm trying to achieve now is closing all children accordion con...