各位观众,大家好!今天咱们来聊聊 JavaScript 里一个挺有意思的小家伙——空值合并运算符(Nullish Coalescing Operator),简称 ??。这玩意儿从 ES2020 开始加入战场,专门用来解决一些关于“空”的问题。别看它长得像个问号,威力可不小,能让你的代码更简洁、更安全。
一、啥是空值合并运算符?
先来个简单的定义:空值合并运算符 ?? 是一种逻辑运算符。当左侧的操作数为 null 或 undefined 时,它会返回右侧的操作数;否则,返回左侧的操作数。
听起来有点绕?没关系,咱们上代码:
let name = null;
let defaultName = "Anonymous";
let displayName = name ?? defaultName;
console.log(displayName); // 输出: "Anonymous"
在这个例子里,name 是 null,所以 name ?? defaultName 就返回了 defaultName,也就是 "Anonymous"。
如果 name 有值呢?
let name = "Alice";
let defaultName = "Anonymous";
let displayName = name ?? defaultName;
console.log(displayName); // 输出: "Alice"
这回 name 是 "Alice",所以 name ?? defaultName 就直接返回了 "Alice"。
二、?? 和 || 的区别:一个“空”的陷阱
可能有人会说:“这玩意儿跟逻辑或 || 好像啊!” 没错,它们乍一看很像,但区别大了去了。|| 是个“真值判断器”,只要左侧的操作数能被转换成 true,它就返回左侧的值。这意味着,false、0、""(空字符串)等等,都会被 || 当成“假”来处理。
let quantity = 0;
let defaultQuantity = 10;
let actualQuantity = quantity || defaultQuantity;
console.log(actualQuantity); // 输出: 10
在这个例子里,quantity 是 0,|| 认为 0 是“假”的,所以返回了 defaultQuantity,也就是 10。但实际上,我们可能希望 quantity 为 0 时,actualQuantity 就是 0,而不是 10。
这时候,?? 就派上用场了:
let quantity = 0;
let defaultQuantity = 10;
let actualQuantity = quantity ?? defaultQuantity;
console.log(actualQuantity); // 输出: 0
?? 只关心左侧是不是 null 或 undefined,0 并不是 null 或 undefined,所以它会直接返回 quantity,也就是 0。
用一张表格来总结一下:
| 运算符 | 左侧操作数 | 返回值 |
|---|---|---|
|| |
null |
右侧操作数 |
|| |
undefined |
右侧操作数 |
|| |
false |
右侧操作数 |
|| |
0 |
右侧操作数 |
|| |
"" |
右侧操作数 |
?? |
null |
右侧操作数 |
?? |
undefined |
右侧操作数 |
?? |
false |
左侧操作数 |
?? |
0 |
左侧操作数 |
?? |
"" |
左侧操作数 |
三、?? 的优先级:小心“左右为难”
在复杂的表达式里,运算符的优先级是个很重要的概念。?? 的优先级相对较低,比 || 和 && 都要低。这意味着,如果你想同时使用 ?? 和 || 或 &&,你需要用括号来明确优先级。
// 错误写法,会报错!
// let result = a || b ?? c;
// 正确写法:
let result = a || (b ?? c);
let result2 = (a ?? b) || c;
为什么会报错呢?因为 JavaScript 引擎不知道你是想先算 a || b 再和 c 做 ?? 运算,还是想先算 b ?? c 再和 a 做 || 运算。所以,为了避免歧义,必须用括号来明确优先级。
四、?? 的实际应用场景:让代码更优雅
-
处理 API 返回值:
很多时候,我们从 API 获取数据,但 API 可能会返回
null或undefined。使用??可以很方便地提供默认值:let userData = await fetchUserData(); // 假设这个函数从 API 获取用户数据 let userName = userData?.name ?? "Guest"; // 使用可选链式调用 ?. 确保 userData 存在 let userAge = userData?.age ?? 18; console.log(`Welcome, ${userName}! You are ${userAge} years old.`); -
配置项默认值:
在配置对象中,有些配置项可能不是必须的。使用
??可以为这些配置项提供默认值:function createButton(options) { let text = options.text ?? "Click Me"; let color = options.color ?? "blue"; let size = options.size ?? "medium"; console.log(`Creating a ${size} ${color} button with text "${text}"`); } createButton({ color: "red" }); // 输出: Creating a medium red button with text "Click Me" createButton({ text: "Submit", size: "large" }); // 输出: Creating a large blue button with text "Submit" createButton({}); // 输出: Creating a medium blue button with text "Click Me" -
函数参数默认值:
虽然 ES6 引入了函数参数默认值,但
??在某些情况下更方便:function greet(name) { name = name ?? "World"; console.log(`Hello, ${name}!`); } greet("Alice"); // 输出: Hello, Alice! greet(null); // 输出: Hello, World! greet(undefined); // 输出: Hello, World! greet(""); // 输出: Hello, ! (注意,这里和使用 `||` 的区别) -
链式调用中的安全访问:
结合可选链式调用
?.,??可以安全地访问深层嵌套的对象属性,并提供默认值:let user = { profile: { address: { city: "New York", }, }, }; let city = user?.profile?.address?.city ?? "Unknown"; console.log(city); // 输出: New York let user2 = {}; // 空对象 let city2 = user2?.profile?.address?.city ?? "Unknown"; console.log(city2); // 输出: Unknown
五、??=:空值赋值运算符
ES2021 又给 ?? 加了个好兄弟——空值赋值运算符 ??=。这个运算符可以将一个变量赋值为右侧的值,当且仅当该变量当前的值为 null 或 undefined 时。
let username; // 声明但未赋值,所以是 undefined
username ??= "Guest";
console.log(username); // 输出: "Guest"
let age = 25;
age ??= 30;
console.log(age); // 输出: 25 (因为 age 已经有值了)
??= 相当于:
let username;
username = username ?? "Guest"; // 等价于 username ??= "Guest";
??= 可以简化代码,避免重复书写变量名。
六、注意事项
??只能用于null和undefined的判断,不能用于其他“假值”的判断。- 在使用
??和||或&&时,务必使用括号来明确优先级,避免歧义。 ??=只能用于变量的赋值,不能用于其他表达式。
七、总结
空值合并运算符 ?? 是 JavaScript 里一个很有用的工具,可以帮助你更简洁、更安全地处理 null 和 undefined 的情况。它和逻辑或 || 的区别在于,?? 只关心 null 和 undefined,而 || 关心所有“假值”。??= 则简化了空值赋值的操作。
掌握了 ??,你的 JavaScript 代码会更加健壮,逻辑会更加清晰。希望今天的讲座能让你对 ?? 有更深入的了解! 咱们下期再见!