可选链和空值合并运算符解决了什么问题?
简化版
可选链 ?. 用来安全访问可能为空的属性、方法或数组项;空值合并 ?? 只在左侧是 null 或 undefined 时使用右侧默认值,避免 || 误伤 0、空字符串和 false。
详细版
可选链常见写法:
user?.profile?.name
list?.[0]
callback?.()
空值合并常见写法:
const pageSize = input.size ?? 20;
它们经常一起使用:
const city = user?.address?.city ?? '未知';
面试重点是:?. 只在当前访问对象为 null/undefined 时短路,?? 只把 null/undefined 当成缺失值。
完整版教学
一、可选链解决深层属性访问的防御代码
没有可选链时,访问深层字段常写成一串 &&。
这种写法冗长,而且容易把真正有效的假值也混入判断。
可选链把“如果前面为空就停止访问”的意图写得更直接。
const name = user && user.profile && user.profile.name;
const name2 = user?.profile?.name;
如果 user 是 null,name2 会得到 undefined,不会抛出错误。
这对接口数据、配置对象、可选回调非常常见。
但它不是异常吞噬器,它只处理链路中 null 和 undefined。
二、可选链有三种常见形态
可选链不仅能访问对象属性,也能访问数组项和调用函数。 不同形态解决的场景不同。 面试时最好把这三种都说出来,说明自己不是只会背属性访问。
user?.name; // 属性
users?.[0]; // 下标
onClose?.(); // 函数调用
| 写法 | 场景 | 为空时结果 |
|---|---|---|
obj?.prop | 对象属性 | undefined |
obj?.[key] | 动态属性或数组项 | undefined |
fn?.() | 可选函数调用 | undefined |
函数调用场景很实用。
组件回调不是必传时,用 onSuccess?.(data) 比先写 if (onSuccess) 更紧凑。
三、短路只发生在连续链路上
可选链短路有边界。 如果你用括号把链路截断,后续访问可能继续执行并报错。 这类细节经常出现在笔试题里。
const user = null;
user?.profile?.name; // undefined
(user?.profile).name; // TypeError
第一行是一条连续可选链。
第二行括号先算出 user?.profile,结果是 undefined,然后再访问 .name,于是报错。
连续链路:user ?. profile ?. name -> 中途为空就停
括号截断:(user ?. profile) . name -> 括号后重新访问
所以可选链不能机械添加,要看访问链路是否完整。
四、空值合并解决默认值误判
|| 会把所有假值都当成需要兜底。
但很多业务里 0、空字符串、false 都是合法值。
?? 只在左侧是 null 或 undefined 时使用右侧默认值。
const a = 0 || 10; // 10
const b = 0 ?? 10; // 0
const c = '' ?? '默认'; // ''
const d = null ?? '默认'; // '默认'
带数字的场景最容易说明差异。
比如分页大小 0 可能表示“不限制”,如果用 || 20 就会被错误改成 20。
五、?. 和 ?? 经常组合,但职责不同
可选链负责安全取值。 空值合并负责缺失兜底。 它们经常连用,但不能混成同一个概念。
const timeout = config?.request?.timeout ?? 3000;
这个表达式可以拆成两步理解:
先取值:config?.request?.timeout
-> 取不到得到 undefined
再兜底:undefined ?? 3000
-> 得到 3000
如果 timeout 明确是 0,结果就是 0。
这比 config && config.request && config.request.timeout || 3000 更符合配置语义。
记忆钩子:
?.管“路上有没有断”,??管“结果是不是空值”。
六、语法限制和工程注意点
可选链不能用于所有位置。
比如不能把 obj?.prop 放在赋值左侧。
因为当对象为空时,语言无法知道应该给谁赋值。
// 错误写法
// user?.name = 'Alice';
另外,?? 和 ||、&& 混用时需要用括号明确优先级。
这能避免读者误判,也能满足语法限制。
const value = (a ?? b) || c;
工程实践里,可选链适合读取数据,不适合掩盖数据契约问题。
如果某个字段按协议必须存在,长期使用 ?. 可能会把上游错误隐藏到更晚才暴露。
七、常见误区与追问
- 误区:可选链会捕获所有异常。 它只在访问对象为
null/undefined时短路,不会捕获函数内部抛出的错误。 - 误区:
??和||完全等价。??只处理null/undefined,||会处理所有假值。 - 误区:用了
?.就说明数据结构可以随便缺失。 必填字段缺失仍是数据契约问题,不能全部靠可选链掩盖。 - 追问:
callback?.()如果 callback 是字符串会怎样? 如果值存在但不是函数,调用仍会报错;可选链不做类型转换。 - 追问:为什么
user?.profile.name仍可能报错? 如果profile可能为空,就需要写成user?.profile?.name,每个不确定节点都要标记。 - 追问:
0 ?? 1和0 || 1结果为什么不同?0不是空值,所以??保留它;但0是假值,所以||使用右侧。
八、加强记忆
可选链和空值合并要分开记:?. 是安全走路径,遇到 null/undefined 停下;?? 是结果兜底,只把 null/undefined 当缺失。面试答题时用 0 ?? 20 的分页例子说明它优于 ||,再补连续链路和函数调用的边界。