跳到主要内容

可选链

可选链解决了什么问题?​

  • 痛点: 访问深层属性时中间某层可能为 null/undefined, 直接取属性抛 TypeError;
  • 旧写法一: user.address ? user.address.street : undefined, 属性路径重复书写, 嵌套更深时几乎不可读;
  • 旧写法二: user.address && user.address.street, 虽能短路, 但 user.address 仍出现多次;
  • 附带问题: document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null 会把查找执行两次;
const user = {}; // 没有 address
user.address.street; // TypeError
user?.address?.street; // undefined, 不报错
  • 定位: ?. 是"安全读取"的语法糖, 缺东西时返回 undefined 而不是报错;
  • 术语约定: 后文说某值"存在"指它不是 null 也不是 undefined;

?. 有哪几种写法?​

写法无 ?. 时等价于左值为 null/undefined 时
obj?.propobj.prop返回 undefined
obj?.[expr]obj[expr]返回 undefined
func?.()func()返回 undefined
obj.method?.()obj.method()返回 undefined
  • 本质: 不是运算符, 而是特殊语法结构, 因此能覆盖点、方括号与调用三种位置;
const userAdmin = { admin: () => "admin" };
const userGuest = {};
userAdmin.admin?.(); // "admin"
userGuest.admin?.(); // undefined, 方法不存在也不报错

const user2 = null;
user2?.["firstName"]; // undefined
  • 混合使用: user?.address?.["street"]、arr?.[0]?.name 等可以逐层衔接;

短路发生在哪一层?​

  • 只保证 ?. 左侧: 在 user?.address.street.name 中仅 user 可为空, .street 与 .name 仍按普通访问求值;
  • 逐层可选: 每一层都允许缺失时, 必须逐层写 ?., 一个 ?. 不会向后传递;
  • 短路副作用: 左侧不存在时, 右侧的函数调用与表达式一概不求值;
let x = 0;
const user = null;
user?.sayHi(x++); // 不进入调用, x++ 不执行
console.log(x); // 0
  • 声明前提: 变量必须先声明, 否则 user?.anything 仍抛 ReferenceError, ?. 只处理值不存在, 不处理标识符不存在;

?. 与 &&、?? 如何配合?​

  • 与 &&: user.address && user.address.street 用真值判断, 0、"" 等假值会被误判为缺失; ?. 只判断 null/undefined;
  • 与 ??: ?. 缺失时得到 undefined, 需要兜底值就接 ??;
const user = {};
user.address?.street ?? "未知街道"; // "未知街道"

可选链能用于赋值与 delete 吗?​

  • 安全删除: 可以, delete user?.name 只在 user 存在时执行删除;
  • 安全写入: 不可以, ?. 不能出现在赋值左侧;
delete user?.name; // user 为空时什么也不做

let user2 = null;
user2?.name = "John"; // TypeError, 等价于 undefined = "John"

什么时候不该用可选链?​

  • 原则: 只在该值"按业务逻辑允许缺失"时使用;
  • 反例: user 按逻辑必然存在而 address 可选时, 写 user.address?.street, 而不是 user?.address?.street;
  • 原因: 过度使用会把编程错误静默成 undefined, 让 bug 更难定位;
  • 兼容性: 属于较新语法, 老环境需转译 (Babel 等);
  • 边界: ?. 与 ?? 的判空口径一致, 与 ==/隐式转换无关, 见 隐式转换与判等;