引言
“Cannot read property 'name' of undefined——又是这个错!我明明已经写了 if (user && user.profile && user.profile.name) 啊!”
同事凑过来看了一眼:“你只检查了 user 和 user.profile,但没检查 user.profile.name 本身?哦,其实 user.profile.name 不可能是 undefined 的……等等,如果 user.profile 是空对象呢?”
我陷入了沉思:难道我要写 user && user.profile && user.profile.name && user.profile.name.firstName?这代码长得像铁轨,谁看得懂啊!
直到有一天,我发现了可选链操作符 ?.。它就像一把瑞士军刀,轻轻一划,所有 undefined 的烦恼都烟消云散。
一、传统防守:&& 的“人肉护盾”
在过去,为了安全地访问深层嵌套的属性,我们不得不写这样的代码:
const firstName = user && user.profile && user.profile.name && user.profile.name.firstName;
如果中间任何一环是 null 或 undefined,整个表达式短路返回 undefined,不会报错。但这写法,读起来像在爬楼梯,每层都要确认一下。
更别提调用可能存在的方法:
const result = api && api.getData && api.getData();
万一 api.getData 不是函数?又得加判断。
二、可选链:.?. 的优雅空降
可选链操作符 ?. 允许你读取位于连接链深处的属性,而无需显式验证每一环是否有效。如果引用是 null 或 undefined,表达式短路返回 undefined。
const firstName = user?.profile?.name?.firstName;
就这么简单!如果 user、profile、name 任何一个不存在,整个表达式返回 undefined,而不是报错。
2.1 函数调用可选链
const result = api?.getData?.();
如果 api 是 null/undefined,或者 api.getData 不是函数,都返回 undefined,不会抛错。
2.2 数组元素可选链
const firstItem = arr?.[0];
如果 arr 不是数组或者是 null/undefined,返回 undefined。
2.3 与空值合并搭配使用
const firstName = user?.profile?.name?.firstName ?? '匿名';
如果最终结果是 undefined 或 null,就换成默认值。完美!
三、实战对比:代码简洁度暴增
场景1:读取深层 API 响应
// 旧写法
const city = response && response.data && response.data.user && response.data.user.address && response.data.user.address.city;
// 新写法
const city = response?.data?.user?.address?.city;
场景2:调用可选回调
// 旧写法
if (onSuccess && typeof onSuccess === 'function') {
onSuccess(data);
}
// 新写法
onSuccess?.(data);
场景3:动态属性名
const value = obj?.[key];
四、注意事项:别滥用
?.只检查左侧是否为null或undefined,不检查false、0、''等假值。如果你需要过滤假值,用||或??。- 不能用于赋值:
obj?.prop = value是语法错误。 - 短路效应:一旦遇到
null/undefined,右侧整个链停止求值,包括函数调用。 - 性能:现代浏览器对
?.优化很好,放心用。
五、兼容性与降级
可选链是 ES2020 特性,现代浏览器都支持(Chrome 80+、Firefox 74+、Safari 13.1+)。如果需要兼容旧浏览器,可以用 Babel 插件 @babel/plugin-proposal-optional-chaining 转译。
六、总结:告别防御性编程噩梦
可选链操作符让 JavaScript 代码变得更加简洁、安全、可读。你不再需要写一长串 && 来保护每一层属性访问,也不用担心 Cannot read property of undefined 半夜叫醒你。
记住:用 ?. 代替 && 链,用 ?? 提供默认值。这两个好基友,能让你的代码年轻十岁。
每日一问:你曾经因为忘记检查深层属性,导致过线上报错吗?或者写过最长的 && 链有多长?评论区晒出你的“防御塔”代码,让大家开开眼!