各位靓仔靓女,早上好/下午好/晚上好(取决于你啥时候看这篇文章)!我是你们的老朋友,今天咱们来聊聊 JavaScript 里那些“偷懒神器”——逻辑赋值运算符。
话说程序员嘛,天生就喜欢偷懒。能少写一行代码,绝对不多写一个字。JavaScript 早就看穿了咱们的小心思,所以贴心地提供了 &&=, ||=, ??= 这三个逻辑赋值运算符。它们能让你的条件赋值语句变得更简洁、更优雅(逼格更高),当然也更容易理解(如果学会了的话)。
一、&&= (逻辑与赋值)—— “短路”赋值
&&= 运算符,可以理解为“如果左边的值是真值,那么就将右边的值赋给左边”。 它的完整形式是:
a &&= b;
这等价于:
a = a && b;
再等价于:
if (a) {
a = b;
}
看起来好像也没省多少代码? 别急,想象一下更复杂的场景。 &&= 的精髓在于它的“短路”特性。 只有当 a 是真值时,才会执行赋值操作。 如果 a 是假值,比如 false、null、undefined、0、"" (空字符串) 或者 NaN,那么 a 的值保持不变,根本不会执行 b 的求值。
示例 1:确保对象存在后再添加属性
假设你有一个对象 user,你只想在 user 存在的情况下,才给它添加一个 age 属性。 以前你可能会这么写:
let user = { name: "张三" }; // 假设 user 可能存在,也可能不存在
if (user) {
user.age = 30;
}
console.log(user); // 输出: { name: '张三', age: 30 }
现在,有了 &&=,一行代码搞定:
let user = { name: "张三" };
user &&= { ...user, age: 30 }; // 展开运算符...是为了创建新的对象,避免修改原对象
console.log(user); // 输出: { name: '张三', age: 30 }
如果 user 一开始是 null 或者 undefined,那么 user 的值会保持不变。
示例 2:优化条件更新
假设你有一个变量 count,你想在某个条件成立时,才将它乘以 2。
let count = 5;
let condition = true;
if (condition) {
count = count * 2;
}
console.log(count); // 输出: 10
使用 &&=:
let count = 5;
let condition = true;
condition &&= count * 2; //condition会变为10
console.log(condition); // 输出: 10
注意,这里condition的值变为了count * 2的结果,因为condition本身是一个变量,赋值运算会修改它的值。 如果你的目的是更新count,你需要稍微调整一下:
let count = 5;
let condition = true;
condition && (count *= 2); // 注意这里使用了括号
console.log(count); // 输出: 10
注意事项:
&&=的优先级低于逻辑与运算符&&,所以要注意添加括号来改变运算顺序。&&=主要用于条件赋值,它并不会直接返回一个布尔值。
二、||= (逻辑或赋值)—— “备胎”赋值
||= 运算符,可以理解为“如果左边的值是假值,那么就将右边的值赋给左边”。 它的完整形式是:
a ||= b;
这等价于:
a = a || b;
再等价于:
if (!a) {
a = b;
}
同样,||= 也具有“短路”特性。 只有当 a 是假值时,才会执行赋值操作。 如果 a 是真值,那么 a 的值保持不变,不会执行 b 的求值。
示例 1:提供默认值
假设你从某个地方获取一个配置项 theme,但如果这个配置项不存在(null 或 undefined),你想给它设置一个默认值 "light"。
let theme = null; // 假设 theme 可能为 null
if (!theme) {
theme = "light";
}
console.log(theme); // 输出: light
使用 ||=:
let theme = null;
theme ||= "light";
console.log(theme); // 输出: light
如果 theme 一开始是 "dark",那么 theme 的值会保持不变。
示例 2:初始化变量
假设你有一个变量 username,你想确保它总有一个值。 如果它一开始是 undefined,你想给它设置一个默认的用户名 "Guest"。
let username; // username 未定义
if (!username) {
username = "Guest";
}
console.log(username); // 输出: Guest
使用 ||=:
let username;
username ||= "Guest";
console.log(username); // 输出: Guest
注意事项:
||=的优先级低于逻辑或运算符||,所以要注意添加括号来改变运算顺序。- 记住
||=是基于假值进行判断的,false、null、undefined、0、""、NaN都会被认为是假值。
三、??= (空值合并赋值)—— “真正”的备胎
??= 运算符,是 ||= 的一个更严格的版本。 它的含义是“只有当左边的值是 null 或者 undefined 时,才将右边的值赋给左边”。 它的完整形式是:
a ??= b;
这等价于:
a = a ?? b;
再等价于:
if (a === null || a === undefined) {
a = b;
}
??= 同样具有“短路”特性。 只有当 a 是 null 或者 undefined 时,才会执行赋值操作。 如果 a 是其他任何值(包括 false、0、""),那么 a 的值保持不变,不会执行 b 的求值。
示例 1:区分 null 和 false
假设你从某个地方获取一个配置项 enabled,它可能的值是 true、false、null 或 undefined。 你希望只有在 enabled 是 null 或 undefined 时,才给它设置一个默认值 true。
let enabled = null; // 假设 enabled 可能为 null
if (enabled === null || enabled === undefined) {
enabled = true;
}
console.log(enabled); // 输出: true
使用 ??=:
let enabled = null;
enabled ??= true;
console.log(enabled); // 输出: true
如果 enabled 一开始是 false,那么 enabled 的值会保持不变。 这是 ??= 和 ||= 的一个关键区别。
示例 2:处理可选属性
假设你有一个对象 options,它可能包含一个可选的属性 timeout。 你想确保 timeout 属性总有一个值,如果没有提供,就使用默认值 1000。
let options = {}; // options 可能没有 timeout 属性
if (options.timeout === null || options.timeout === undefined) {
options.timeout = 1000;
}
console.log(options.timeout); // 输出: 1000
使用 ??=:
let options = {};
options.timeout ??= 1000;
console.log(options.timeout); // 输出: 1000
注意事项:
??=的优先级低于空值合并运算符??,所以要注意添加括号来改变运算顺序。??=只会判断null和undefined,其他任何值都会被认为是有效值。 这是它与||=的本质区别。
四、 总结与对比
为了方便大家理解,我把这三个运算符的特性总结成一个表格:
| 运算符 | 含义 | 短路特性 | 适用场景 |
|---|---|---|---|
&&= |
如果左边的值是真值,那么就将右边的值赋给左边。 | 只有当左边的值是真值时,才会执行赋值操作。 | 条件赋值,确保对象存在后再添加属性,优化条件更新。 |
||= |
如果左边的值是假值,那么就将右边的值赋给左边。 | 只有当左边的值是假值时,才会执行赋值操作。 false、null、undefined、0、""、NaN 都会被认为是假值。 |
提供默认值,初始化变量。 |
??= |
只有当左边的值是 null 或者 undefined 时,才将右边的值赋给左边。 |
只有当左边的值是 null 或者 undefined 时,才会执行赋值操作。 |
区分 null 和 false,处理可选属性。 |
选择哪个运算符?
- 如果你想在某个条件成立时才赋值,使用
&&=。 - 如果你想提供一个默认值,但任何真值都应该保留,使用
||=。 - 如果你想提供一个默认值,但只有
null或undefined应该被替换,使用??=。
五、 实际应用案例
案例 1:React 组件中的默认 Props
在 React 组件中,我们经常需要为 props 提供默认值。 使用 ??= 可以很方便地实现:
function MyComponent(props) {
const { name, age } = props;
const defaultName = "Guest";
const defaultAge = 18;
const finalName = name ?? defaultName;
const finalAge = age ?? defaultAge;
return (
<div>
Hello, {finalName}! You are {finalAge} years old.
</div>
);
}
// 或者更简洁的方式
function MyComponent({ name ??= "Guest", age ??= 18 }) {
return (
<div>
Hello, {name}! You are {age} years old.
</div>
);
}
export default MyComponent;
案例 2:处理 API 返回的数据
假设你从 API 获取到一个用户对象,但某些字段可能为空。 使用 ??= 可以避免程序出错:
async function getUser() {
const response = await fetch("/api/user");
const user = await response.json();
const username = user.name ?? "Unknown";
const email = user.email ?? "N/A"; // 如果email为null或者undefined,就显示"N/A"
console.log(`Username: ${username}, Email: ${email}`);
}
getUser();
案例 3:缓存计算结果
如果你有一个计算量很大的函数,你可以使用 &&= 来缓存计算结果,避免重复计算:
let cachedResult = null;
function expensiveCalculation() {
console.log("Performing expensive calculation...");
// 模拟一个耗时的计算
return Math.random() * 1000;
}
function getResult() {
cachedResult &&= expensiveCalculation(); // 如果 cachedResult 已经有值,就保持不变。 否则就计算结果。
return cachedResult;
}
console.log(getResult()); // 第一次调用,会执行 expensiveCalculation()
console.log(getResult()); // 第二次调用,直接返回 cachedResult,不会执行 expensiveCalculation()
六、 注意事项和陷阱
- 优先级问题: 一定要注意这三个运算符的优先级。 如果你不确定,最好加上括号。
- 类型转换: JavaScript 是一门动态类型语言,所以要小心类型转换带来的意外结果。 例如,
0 ||= "default"会将0替换为 "default",因为0是假值。 - 可读性: 虽然这些运算符可以简化代码,但过度使用可能会降低代码的可读性。 请根据实际情况权衡利弊。
- 浏览器兼容性: 虽然目前主流浏览器都已经支持这些运算符,但为了兼容旧版本的浏览器,可能需要使用 Babel 等工具进行转换。
七、 总结
&&=, ||=, ??= 这三个逻辑赋值运算符是 JavaScript 提供给我们的“偷懒神器”。 它们可以简化条件赋值语句,提高代码的效率和可读性(前提是理解了它们的用法)。 掌握了这些运算符,你就可以写出更简洁、更优雅的 JavaScript 代码,成为一个更优秀的程序员(至少看起来是这样)。
希望今天的讲座对大家有所帮助。 记住,编程的乐趣在于不断学习和探索。 继续加油,各位!