问题还原
这是最近 CR 的时候在业务代码中发现了一个问题,先来看一下问题代码:
// data 为接口返回的数据
const { bizObject = {}, total = 0 } = data.result || {};
const list = bizObject.list || [];
// 其他逻辑,比如把 list 更新到 state 中,等等
- A 接口正常的情况:
data
中有result
属性,且result
对象中bizObject
返回了一个数—— ✅ - B 接口异常:
data
对象中没有result
属性 —— ✅ - 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)