JS `Logical Assignment Operators (&&=, ||=, ??=)`:简化变量条件赋值

各位靓仔靓女,早上好/下午好/晚上好(取决于你啥时候看这篇文章)!我是你们的老朋友,今天咱们来聊聊 JavaScript 里那些“偷懒神器”——逻辑赋值运算符。

话说程序员嘛,天生就喜欢偷懒。能少写一行代码,绝对不多写一个字。JavaScript 早就看穿了咱们的小心思,所以贴心地提供了 &&=, ||=, ??= 这三个逻辑赋值运算符。它们能让你的条件赋值语句变得更简洁、更优雅(逼格更高),当然也更容易理解(如果学会了的话)。

一、&&= (逻辑与赋值)—— “短路”赋值

&&= 运算符,可以理解为“如果左边的值是真值,那么就将右边的值赋给左边”。 它的完整形式是:

a &&= b;

这等价于:

a = a && b;

再等价于:

if (a) {
  a = b;
}

看起来好像也没省多少代码? 别急,想象一下更复杂的场景。 &&= 的精髓在于它的“短路”特性。 只有当 a 是真值时,才会执行赋值操作。 如果 a 是假值,比如 falsenullundefined0"" (空字符串) 或者 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,但如果这个配置项不存在(nullundefined),你想给它设置一个默认值 "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

注意事项:

  • ||= 的优先级低于逻辑或运算符 ||,所以要注意添加括号来改变运算顺序。
  • 记住 ||= 是基于假值进行判断的,falsenullundefined0""NaN 都会被认为是假值。

三、??= (空值合并赋值)—— “真正”的备胎

??= 运算符,是 ||= 的一个更严格的版本。 它的含义是“只有当左边的值是 null 或者 undefined 时,才将右边的值赋给左边”。 它的完整形式是:

a ??= b;

这等价于:

a = a ?? b;

再等价于:

if (a === null || a === undefined) {
  a = b;
}

??= 同样具有“短路”特性。 只有当 anull 或者 undefined 时,才会执行赋值操作。 如果 a 是其他任何值(包括 false0""),那么 a 的值保持不变,不会执行 b 的求值。

示例 1:区分 nullfalse

假设你从某个地方获取一个配置项 enabled,它可能的值是 truefalsenullundefined。 你希望只有在 enablednullundefined 时,才给它设置一个默认值 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

注意事项:

  • ??= 的优先级低于空值合并运算符 ??,所以要注意添加括号来改变运算顺序。
  • ??= 只会判断 nullundefined,其他任何值都会被认为是有效值。 这是它与 ||= 的本质区别。

四、 总结与对比

为了方便大家理解,我把这三个运算符的特性总结成一个表格:

运算符 含义 短路特性 适用场景
&&= 如果左边的值是真值,那么就将右边的值赋给左边。 只有当左边的值是真值时,才会执行赋值操作。 条件赋值,确保对象存在后再添加属性,优化条件更新。
||= 如果左边的值是假值,那么就将右边的值赋给左边。 只有当左边的值是假值时,才会执行赋值操作。 falsenullundefined0""NaN 都会被认为是假值。 提供默认值,初始化变量。
??= 只有当左边的值是 null 或者 undefined 时,才将右边的值赋给左边。 只有当左边的值是 null 或者 undefined 时,才会执行赋值操作。 区分 nullfalse,处理可选属性。

选择哪个运算符?

  • 如果你想在某个条件成立时才赋值,使用 &&=
  • 如果你想提供一个默认值,但任何真值都应该保留,使用 ||=
  • 如果你想提供一个默认值,但只有 nullundefined 应该被替换,使用 ??=

五、 实际应用案例

案例 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 代码,成为一个更优秀的程序员(至少看起来是这样)。

希望今天的讲座对大家有所帮助。 记住,编程的乐趣在于不断学习和探索。 继续加油,各位!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注