侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 229 篇文章
  • 累计收到 7 条评论

可选链遇到括号,短路就到此为止

2026-10-22 / 0 评论 / 6 阅读

可选链遇到括号,短路就到此为止

const state = {};
console.log(state.user?.name);       // undefined
console.log((state.user?.name).length); // TypeError

两行看起来只多了一对括号,结果却从 undefined 变成异常。把这段放进界面渲染,空数据就可能让整块组件直接报错。可选链的短路范围,比括号外观给人的印象要窄。

括号把访问拆成两段

state.user?.name 是一段连续的可选链。读取 state.user 得到 undefined 后,后面的 .name 不再执行,表达式结果就是 undefined。

括号版本先计算 (state.user?.name)。这一步确实会短路并返回 undefined,但括号已经结束了。后面的 .length 是另一次普通属性访问,等于对 undefined 读取属性,于是抛出 TypeError。

可选链按语法边界工作,括号明确把这条链分开。代码里如果需要把中间结果分组,后续访问仍要单独检查,不能顺手当成受保护的部分。

null 与 undefined 都会停下

在 Node 本机脚本里,缺少根对象、根对象为 null、对象存在但子项缺失,这几种情况对 ?. 的访问都得到 undefined。子项存在时则正常拿到 Lin。

missing root: undefined
null root: undefined
missing child: undefined
present child: Lin

可选链会检查属性访问左侧的值是不是 null 或 undefined。当值是 0、false 或空字符串时,访问照常继续;如果继续取属性,结果可能是 undefined,但不会因为这些值本身而短路。

这个区别在模板数据里很实用。count?.toString() 面对 0 仍会调用方法;若需求是值为空时走默认,可选链并不承担这个判断。

默认值选什么,取决于空值语义

拿到结果后,常见写法有 || 和 ??。前者会把 0、false、"" 一并看作需要兜底的值;后者只在左边是 null 或 undefined 时用默认项。对计数、开关或允许空文本的输入,差别很大。

验证脚本各调用一次默认函数:null ?? next() 与 0 || next() 都调用了它,所以计数结果是 2。第一处因为空值需要替代,第二处则是 0 被 || 判断为假。换成 0 ?? next(),默认函数不会调用,原值也会保留。

nullish default: fallback
falsy default: fallback
fallback calls: 2

默认值不是越短越好,先想清楚空具体指什么。表单数字 0 往往是有效值;从接口来的 null 才可能代表尚未提供。把两者塞进同一个 ||,问题不在语法,而在把业务状态合并了。比如搜索框允许空字符串时,query || cachedQuery 会把用户主动清空输入误当成没有输入,显示旧缓存。这个结果通常比报错更难察觉。

链式访问仍要给结果做处理

?. 让某段路径可以缺失,不会自动把最终结果变成安全的字符串或数组。profile?.name.trim() 里,若 profile 有效但 name 是 undefined,普通的 .trim() 仍会报错。若 name 自身也可能空,保护点要写在它左侧,例如 profile?.name?.trim()。

这里容易写出一串问号来求稳。我的取舍是只在数据契约允许字段缺省的位置加保护,其他情况尽早暴露异常。接口数据可以在进入组件前集中校验,保证必填字段不缺;组件内再处理真正允许缺省的字段。若把每个属性都可选化,接口字段拼错也可能静悄悄变成空白,排查反而更费劲。

同理,items?.map(renderItem) 能处理 items 缺失,却不能处理 items 是一个字符串或普通对象。可选链只防空值访问,不负责校验类型。外部数据的形状应在入口处理,组件内部再按已经确认的约定渲染。

界面代码里的边界

读取可能不存在的嵌套字段时,可选链能让页面不因空对象立即中断。若缺失意味着数据错误,就不要用它把问题藏起来;若缺失是正常状态,再明确给出展示兜底。括号后的访问、方法调用以及默认值策略,都应按各自语义单独检查。

记法可以很短,判断不能省。

    🤞 分享