JavaScript 可选链(Optional Chaining)笔记

一、可选链的引入背景与核心问题(对应视频 00:00-03:15)

1.1 传统属性访问的痛点

当访问嵌套对象的属性时,若中间某一层属性不存在(值为undefinednull),直接访问会触发类型错误

  • 示例场景:获取餐厅(

    restaurant

    )周一(

    1
    mon

    )的营业时间(

    1
    open

    ),对象结构如下:

    javascript

    1
    2
    3
    4
    5
    6
    7
    const restaurant = {
    openingHours: {
    thu: { open: 12, close: 22 },
    fri: { open: 11, close: 23 },
    sat: { open: 0, close: 24 }, // 0表示24小时营业
    },
    };
  • 问题代码:直接访问

    1
    restaurant.openingHours.mon.open

    由于

    1
    openingHours

    中没有

    1
    mon

    属性,

    1
    restaurant.openingHours.mon

    会返回

    1
    undefined

    ,后续访问

    1
    open

    会报错:

    1
    Uncaught TypeError: Cannot read properties of undefined (reading 'open')

1.2 传统解决方案的局限性

为避免报错,需手动逐层检查属性是否存在,代码冗余且可读性差,嵌套层级越多,问题越突出。

  • 传统写法:

    javascript

    1
    2
    3
    4
    5
    6
    // 需先检查openingHours存在,再检查mon存在
    if (restaurant.openingHours && restaurant.openingHours.mon) {
    console.log(restaurant.openingHours.mon.open);
    } else {
    console.log("Closed");
    }
  • 缺点:若嵌套层级增加(如restaurant.location.address.city),需叠加更多条件判断,代码臃肿。

二、可选链的基本概念与语法(对应视频 03:16-06:25)

2.1 定义与语法

  • 定义:可选链(?.)是 ES2020 引入的特性,用于安全访问嵌套对象 / 数组的属性或方法。若?.左侧的表达式值为nullundefined(即 “空值”),则立即返回undefined,不再继续访问后续属性,避免报错。

  • 语法规则

    • 访问对象属性:obj?.prop(等价于obj && obj.prop,但更简洁)
    • 访问嵌套属性:obj?.prop1?.prop2(支持多层嵌套,任意一层为空则返回undefined
    • 动态属性访问:obj?.[dynamicProp](结合括号表示法,支持变量作为属性名)

2.2 基础使用示例

基于 1.1 中的restaurant对象,用可选链优化属性访问:

javascript

1
2
3
4
5
6
7
8
9
10
11
// 1. 访问单层可选属性:检查openingHours是否存在,再访问mon
const monOpen = restaurant.openingHours?.mon?.open;
console.log(monOpen); // undefined(无报错)

// 2. 访问存在的属性:fri存在,正常返回11
const friOpen = restaurant.openingHours?.fri?.open;
console.log(friOpen); // 11

// 3. 处理空值:用空值合并运算符(??)设置默认值(仅空值触发)
const monStatus = restaurant.openingHours?.mon?.open ?? "Closed";
console.log(monStatus); // Closed(因monOpen为undefined)

2.3 关键注意点

  • 可选链仅判断 “空值”(null/undefined),对0''false等 “假值” 不生效,仍会继续访问后续属性。
  • 示例:restaurant.openingHours?.sat?.open返回00是有效值,非空值),而非undefined

三、可选链在动态场景中的应用(对应视频 06:26-10:59)

3.1 动态属性访问(结合数组与变量)

当属性名通过变量动态生成(如从数组中获取)时,需用?.[变量]语法,可选链同样适用。

  • 示例场景:循环数组

    1
    weekdays

    ,获取餐厅每天的营业时间:

    javascript

    1
    2
    3
    4
    5
    6
    7
    const weekdays = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];

    weekdays.forEach((day) => {
    // 动态访问:day为变量,用?.[day]检查属性是否存在
    const open = restaurant.openingHours?.[day]?.open ?? "Closed";
    console.log(`${day}: ${open}`);
    });
  • 输出结果:

    plaintext

    1
    2
    3
    4
    5
    6
    7
    mon: Closed
    tue: Closed
    wed: Closed
    thu: 12
    fri: 11
    sat: 0
    sun: Closed

3.2 对比传统写法

若不用可选链,需手动检查openingHours[day]是否存在,代码如下:

javascript

1
2
3
4
5
6
7
8
9
weekdays.forEach((day) => {
let open;
if (restaurant.openingHours && restaurant.openingHours[day]) {
open = restaurant.openingHours[day].open;
} else {
open = "Closed";
}
console.log(`${day}: ${open}`);
});
  • 结论:可选链将 3 行判断简化为 1 行,代码更简洁,可读性更高。

四、可选链与空值合并运算符的配合(对应视频 10:00-11:15)

4.1 为何需要搭配使用

  • 问题:若用||设置默认值,会误将0''等 “假值” 当作 “空值” 处理。例如restaurant.openingHours.sat.open0,用||会返回默认值'Closed',与实际业务逻辑冲突。
  • 解决方案:空值合并运算符(??)仅在左侧为null/undefined时返回右侧默认值,与可选链搭配可精准处理默认值场景。

4.2 实战示例

javascript

1
2
3
4
5
6
7
// 错误写法:用||,sat的open=0会被当作假值,返回'Closed'
const satOpenWrong = restaurant.openingHours?.sat?.open || "Closed";
console.log(satOpenWrong); // Closed(错误,实际应返回0)

// 正确写法:用??,仅当值为null/undefined时返回'Closed'
const satOpenRight = restaurant.openingHours?.sat?.open ?? "Closed";
console.log(satOpenRight); // 0(正确)

4.3 常见使用场景

  • 接口数据处理:当接口返回的嵌套字段可能缺失时,用obj?.field1?.field2 ?? '默认值'确保代码不报错且显示合理。
  • 表单值获取:input?.value ?? ''(避免input元素未渲染时访问value报错,且空输入时返回空字符串)。

五、可选链在方法调用中的应用(对应视频 11:16-13:20)

5.1 核心作用

检查方法是否存在后再调用,避免因调用 “非函数”(如undefined)而报错。

  • 语法:obj?.methodName(参数)(若obj.methodName不存在或非函数,返回undefined,不执行调用)

5.2 示例演示

基于restaurant对象,模拟方法存在 / 不存在的场景:

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const restaurant = {
openingHours: {
/* 同前 */
},
// 存在的方法:点单
order(food1, food2) {
return `您点了${food1}${food2}`;
},
// 不存在的方法:点烩饭(orderRisotto未定义)
};

// 1. 调用存在的方法:正常执行
const order1 = restaurant.order?.("汉堡", "薯条") ?? "点单失败";
console.log(order1); // 您点了汉堡和薯条

// 2. 调用不存在的方法:返回undefined,再触发默认值
const order2 = restaurant.orderRisotto?.("蘑菇烩饭") ?? "该菜品暂不提供";
console.log(order2); // 该菜品暂不提供

// 3. 若不用可选链:直接调用不存在的方法会报错
// restaurant.orderRisotto('蘑菇烩饭'); // Uncaught TypeError: restaurant.orderRisotto is not a function

5.3 注意事项

  • 可选链仅检查 “方法是否存在”,不检查 “方法是否可调用”(如obj.method是普通变量而非函数时,仍会报错)。

  • 示例:

    javascript

    1
    2
    const restaurant = { order: "这是字符串,不是方法" };
    // restaurant.order?.() 会报错:Uncaught TypeError: restaurant.order is not a function

六、可选链在数组中的应用(对应视频 13:21-16:04)

6.1 核心作用

检查数组元素是否存在,避免因访问 “越界索引” 或 “空数组” 的元素而报错。

  • 语法:
    • 访问指定索引:arr?.[index](若arrnull/undefined,或index超出数组长度,返回undefined
    • 访问嵌套元素:arr?.[index]?.prop(数组元素为对象时,安全访问其属性)

6.2 示例演示

场景 1:数组非空且元素存在

javascript

1
2
3
4
5
6
7
8
9
10
11
12
const users = [
{ name: "Jonas", email: "jonas@example.com" },
{ name: "Alice", email: "alice@example.com" },
];

// 访问索引0的name:存在,返回Jonas
const userName1 = users?.[0]?.name ?? "用户不存在";
console.log(userName1); // Jonas

// 访问索引1的email:存在,返回alice@example.com
const userEmail1 = users?.[1]?.email ?? "邮箱未设置";
console.log(userEmail1); // alice@example.com

场景 2:数组空或索引越界

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const emptyUsers = []; // 空数组

// 1. 访问空数组的索引0:返回undefined,触发默认值
const userName2 = emptyUsers?.[0]?.name ?? "用户列表为空";
console.log(userName2); // 用户列表为空

// 2. 访问非空数组的越界索引(索引2):返回undefined,触发默认值
const userName3 = users?.[2]?.name ?? "该用户不存在";
console.log(userName3); // 该用户不存在

// 3. 数组本身为undefined:返回undefined,触发默认值
const undefinedUsers = undefined;
const userName4 = undefinedUsers?.[0]?.name ?? "用户数据未加载";
console.log(userName4); // 用户数据未加载

6.3 对比传统写法

传统方式需先检查数组是否存在且长度大于索引,代码如下:

javascript

1
2
3
4
5
6
7
8
9
const getUserName = (arr, index) => {
if (arr && arr.length > index) {
return arr[index].name ?? "邮箱未设置";
}
return "用户不存在";
};

console.log(getUserName(users, 0)); // Jonas
console.log(getUserName(emptyUsers, 0)); // 用户不存在
  • 结论:可选链将条件判断简化为arr?.[index]?.name ?? '默认值',代码更简洁。

七、总结与常见误区(对应视频 16:05-16:11)

7.1 核心知识点总结

应用场景 语法示例 核心作用
对象属性访问 obj?.prop/obj?.prop1?.prop2 安全访问嵌套属性,空值返回undefined
动态属性访问 obj?.[dynamicProp] 变量作为属性名时,避免属性不存在报错
方法调用 obj?.method(参数) 检查方法存在后调用,避免非函数调用报错
数组元素访问 arr?.[index]/arr?.[i]?.prop 安全访问数组元素,避免越界或空数组报错
配合默认值 obj?.prop ?? '默认值' 仅空值触发默认值,不影响0/''等假值

7.2 常见误区

  1. 误区 1:认为可选链可替代所有条件判断
    • 错误:可选链仅处理 “空值”,若需判断 “值是否符合业务规则”(如age > 18),仍需额外条件。
    • 示例:const isAdult = user?.age > 18 ?? false(若user.age17,返回false,正确;若user为空,返回false,正确)。
  2. 误区 2:过度使用可选链
    • 错误:对确定存在的属性(如自己定义的本地对象)使用?.,增加不必要的语法开销。
    • 示例:const name = user.nameuser是本地定义的对象,确定有name属性,无需user?.name)。
  3. 误区 3:混淆?.&&
    • 区别:&&会对所有 “假值” 返回左侧(如obj.prop = 0时,obj && obj.prop返回0obj?.prop也返回0,效果一致);但?.更简洁,且支持多层嵌套(obj?.p1?.p2 vs obj && obj.p1 && obj.p1.p2)。

7.3 最佳实践

  • 接口数据处理:对后端返回的不确定字段(如res.data?.list?.[0]?.id)必用可选链。
  • DOM 操作:访问可能未渲染的元素(如document.querySelector('.modal')?.style.display)必用可选链。
  • 本地对象:确定存在的属性不用可选链,不确定的属性(如动态生成的对象)用可选链。