可选链 ?. 和逻辑赋值 ??= / ||= / &&=。
核心两个:
① ?.——属性不存在不报错,直接返回 undefined
② ??=——只有 null/undefined 才赋值
③ ||=——只有 falsy 才赋值
坑:?. 比 && 链更短、?? 和 || 区别(?? 只对 null/undefined)、可选链不能用来赋值。
以前读深层数据 config.db.host,得每一层都问一句"有没有",写一长串 &&。?. 一放,自动帮你问。
// 一个套娃对象:config 里有 db,db 里有 host
let config = { db: { host: "localhost" } };
// 以前:每层都要 && 判断"这一层有没有"
// 一层层 && 问过去,空了就断
let host = config && config.db && config.db.host;
// ^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^
// config有吗? db有吗? db.host 有吗?
// localhost
console.log(host);
// 现在:?. 一放,中间任何一环是空的就返回 undefined
// ?. 像防撞条:中间哪层空了就直接停,不报错
let host = config?.db?.host;
// ^^ ^^ 中间没有就停下,不报错
// localhost
console.log(host);
// undefined(x 不存在,但不报错!)
console.log(config?.x?.y);
?. 是"安全访问":链上任何一环是 null / undefined,就短路返回 undefined,继续往下读会直接报错。解析接口返回的深层数据(res.data.user.name)时最常用,不用写一长串 &&。
不仅读属性,调方法也能加 ?.:方法不存在就不调,返回 undefined。
// 空对象,里面啥方法都没有
let obj = {};
// obj.fn?. —— fn 不存在就不调用,返回 undefined,不报错
// obj.fn —— fn 不存在会报错:fn is not a function
||= 是"左边是假值就换成右边",&&= 是"左边是真值就换成右边",??= 是"左边是 null/undefined 才换"。都是 a = a 运算 b 的简写。
// 以前 // 左边是假值 let a = false; // 左边是假 → 赋 true a = a || true; // 现在 // 再来一遍 let a = false; // 等价于 a = a || true a ||= true;
|| 看的是"是不是假值"(0、""、false、NaN 都算假),?? 只看"是不是 null / undefined"。
// 0 是有效值 let n = 0; // || 把 0 当假,换成 100 n ||= 100; // 100(0 被当成"假",被换掉了!) console.log(n); // 0 是有效值 let m = 0; // ?? 只认 null/undefined,0 不动 m ??= 100; // 0(0 是有效值,保留) console.log(m);
如果你用 ||= 给用户配置兜底,用户传了 0(表示"不限制")也会被悄悄换成默认值。这就是线上常见的隐性 bug——给默认值时优先用 ??=,它只在 null/undefined 时才动手。
① 可能在什么地方用:读接口深层字段(res?.data?.user?.name)、调用可选方法(obj.fn?.)、组件 props 兜底(props.title ?? "默认")、给配置对象补默认值。
② 常见的问题:用 ||= 误伤 0 和 "";把 ?. 写成 ? ? 中间有空格;对整个表达式误加 ?. 导致可读性差。
③ 解决思路:兜底一律用 ?? / ??=,别用 ||;?. 紧跟属性名无空格;链式访问控制在 3 层以内,太深就先把中间变量取出来。
| API | 作用 | 参数 | 返回值 | 代码示例 |
|---|---|---|---|---|
| ?. | 链上任何一环是空值就直接停下 | 对象?.属性 | 值或 undefined | obj?.a?.b; |
| ?? | 只在左边是 null/undefined 时才取右边 | 左, 右 | 结果值 | a ?? b; |
| ||= | 左边是假值就换成右边(会误伤 0) | 左, 右 | 结果值 | a ||= 100; |
| ??= | 只在左边是 null/undefined 时才赋值 | 左, 右 | 结果值 | a ?? b; |