JS `Nullish Coalescing Operator (??)` 区别于 `||` 的精确性

好的,各位靓仔靓女,老少爷们!欢迎来到今天的“JS冷知识与实用技巧”专场。今天咱们不聊框架,不啃源码,就聊聊JS里两个长得像双胞胎,但脾气秉性截然不同的操作符:|| (逻辑或) 和 ?? (Nullish Coalescing Operator,空值合并运算符)。

很多人觉得这俩货差不多,都是用来给变量设置默认值的。嗯,表面上看是这样。但如果你深入了解它们,你会发现,?? 这家伙更加“精确”、“严谨”,甚至有点“强迫症”。今天我们就来扒一扒它们的底裤,看看它们到底有啥不同。

第一幕:|| 的“宽松”策略

||,逻辑或,老牌选手了。它的规则很简单:只要左边的值能被转换成true,就返回左边的值;否则,返回右边的值。问题就出在“能被转换成true”这个概念上。

在JS的世界里,有一群被称为“falsy”的值,它们在布尔上下文中会被当成false。这些家伙包括:

  • false (废话)
  • 0 (数字零)
  • -0 (负零,冷知识,很少用到)
  • 0n (BigInt 类型的零)
  • "" (空字符串)
  • null (空值)
  • undefined (未定义)
  • NaN (非数字)

也就是说,只要左边的值是这八个“falsy”值中的任何一个,|| 都会毫不犹豫地返回右边的值。

举个栗子:

let quantity = 0;
let defaultQuantity = 10;

let finalQuantity = quantity || defaultQuantity;

console.log(finalQuantity); // 输出 10

在这个例子里,quantity0,一个“falsy”值。所以 quantity || defaultQuantity 的结果是 defaultQuantity,也就是 10。这看起来很合理,对吧?

但是,如果 quantity 就是想设置成 0 呢?比如,用户明确选择了“零”个商品,或者某个计算结果就是 0。这时候,|| 就帮倒忙了,它会错误地把 0 替换成默认值 10

再来一个例子:

let userName = ""; // 用户名为空字符串
let defaultUserName = "Guest";

let finalUserName = userName || defaultUserName;

console.log(finalUserName); // 输出 "Guest"

同样的问题,如果用户就是不想填用户名,或者用户名被清空了,|| 会错误地将其替换成 "Guest"。

第二幕:?? 的“精准”打击

??,Nullish Coalescing Operator,空值合并运算符,是ES2020的新成员。它比 || 更加严格,只会在左边的值是 null 或者 undefined 的时候,才返回右边的值。其他任何值,哪怕是 0""NaN?? 都会原封不动地返回。

用上面的例子,我们把 || 换成 ?? 试试:

let quantity = 0;
let defaultQuantity = 10;

let finalQuantity = quantity ?? defaultQuantity;

console.log(finalQuantity); // 输出 0

这次,finalQuantity 的值是 0,而不是 10。因为 quantity 的值是 0,它不是 nullundefined,所以 ?? 直接返回了 0

再看用户名:

let userName = ""; // 用户名为空字符串
let defaultUserName = "Guest";

let finalUserName = userName ?? defaultUserName;

console.log(finalUserName); // 输出 ""

finalUserName 的值是 "",空字符串。?? 同样没有进行替换。

看到了吗??? 只关心是不是 nullundefined,其他一概不理。这使得它在处理一些需要区分 0""null/undefined 的场景时,更加准确。

第三幕:适用场景大PK

那么,||?? 各自适合哪些场景呢?

特性/场景 ` ` (逻辑或) ?? (空值合并运算符)
判断标准 左侧表达式是否为 "falsy" (false, 0, "", null, undefined, NaN) 左侧表达式是否为 nullundefined
适用场景 只需要一个简单的布尔值,不关心具体的值的场景。 例如:if (variable someDefaultValue) {…} 需要区分 0""null/undefined 的场景。 需要为变量提供默认值,但只在变量为 nullundefined 时才使用默认值。
举例 enableFeature = userSettings.enableFeature || true; (如果 userSettings.enableFeature 未定义或为 false,则启用功能) userName = userSettings.userName ?? "Guest"; (只有当 userSettings.userNamenullundefined 时,才使用 "Guest" 作为默认用户名)
优点 兼容性好,所有浏览器都支持。 更加精确,避免了不必要的默认值替换。
缺点 容易误判,将 0"" 等 "falsy" 值也当成需要替换的值。 兼容性稍差,需要较新的浏览器版本支持。

总结一下:

  • || 的时候: 你不在乎 0"" 这些值,只要它们是“falsy”的,你就想用默认值。比如,你想确保一个布尔值始终是 true,即使原始值是 false0""nullundefinedNaN

  • ?? 的时候: 你很在意 0"" 这些值,它们是有意义的,你只想在变量是 nullundefined 的时候才用默认值。比如,你想为用户的年龄设置默认值,但如果用户明确输入了 0 岁,你就不应该用默认值。

第四幕:优先级和组合使用

需要注意的是,?? 的优先级很低,比 ||&& 等等都要低。所以,在使用 ?? 的时候,最好用括号把它括起来,避免出现意想不到的结果。

例如:

let result = null ?? 1 || 2; // 语法错误!
let result = (null ?? 1) || 2; // 正确,输出 1
let result = null ?? (1 || 2); // 正确,输出 1

另外,?? 不能直接和 &&|| 混用,除非你用括号明确指定优先级。这是为了避免歧义,让代码更易读。

例如:

// let result = a || b ?? c; // 语法错误!
let result = a || (b ?? c); // 正确
let result = (a || b) ?? c; // 正确

第五幕:实战演练

来几个更实际的例子,加深理解:

  • 读取配置文件:

    假设你从一个配置文件中读取一些配置项,有些配置项可能不存在。

    const config = {
      apiEndpoint: "https://example.com/api",
      timeout: 10000,
      maxRetries: 3,
      // apiKey: null, // apiKey 可能不存在,或者显式设置为 null
    };
    
    const API_ENDPOINT = config.apiEndpoint; // 不需要默认值,因为是必须的
    const TIMEOUT = config.timeout ?? 5000; // 如果 timeout 未定义,则使用 5000
    const MAX_RETRIES = config.maxRetries ?? 1; // 如果 maxRetries 未定义,则使用 1
    const API_KEY = config.apiKey ?? "default_api_key"; // 只有当 apiKey 为 null 或 undefined 时,才使用默认值
    const ENABLE_DEBUG = config.enableDebug || false; // 如果 enableDebug 是 undefined, null, false, 0, "" 或 NaN,则启用调试模式
    console.log(API_ENDPOINT, TIMEOUT, MAX_RETRIES, API_KEY, ENABLE_DEBUG);

    在这个例子中,?? 确保了只有当配置项确实不存在(nullundefined)时,才使用默认值。 || 则用于处理布尔值的情况。

  • 处理用户输入:

    假设你正在处理一个表单,用户可以输入一些信息,有些字段是可选的。

    function processForm(formValues) {
      const name = formValues.name || "Anonymous"; // 如果 name 为空字符串,也使用 "Anonymous"
      const age = formValues.age ?? 18; // 只有当 age 为 null 或 undefined 时,才使用 18
      const city = formValues.city ?? "Unknown"; // 只有当 city 为 null 或 undefined 时,才使用 "Unknown"
    
      console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
    }
    
    processForm({ name: "", age: 0 }); // 输出:Name: Anonymous, Age: 0, City: Unknown
    processForm({ age: null, city: undefined }); // 输出:Name: Anonymous, Age: 18, City: Unknown
    processForm({}); // 输出:Name: Anonymous, Age: 18, City: Unknown

    在这个例子中,?? 保证了只有当用户没有提供年龄或城市信息时,才使用默认值。而 || 用于处理姓名为空字符串的情况。

第六幕:兼容性考虑

?? 是 ES2020 的新特性,所以一些老旧的浏览器可能不支持。如果你需要兼容老版本浏览器,可以使用 Babel 等工具进行转译,或者使用 || 来代替,但要注意其潜在的误判问题。

第七幕:总结与建议

总而言之,||?? 都是用来提供默认值的,但它们有着不同的判断标准。|| 更加宽松,而 ?? 更加精准。

  • 选择 || 的时候,要清楚它会将所有 "falsy" 值都视为需要替换的值。
  • 选择 ?? 的时候,要确保你的目标是只在 nullundefined 的情况下才使用默认值。

在实际开发中,要根据具体的场景选择合适的操作符,避免出现意想不到的错误。

希望今天的讲座能帮助大家更好地理解 ||?? 的区别。下次再遇到它们,你就可以自信地说:“哼,我早就看穿你们的底细了!”

感谢各位的聆听,下课!

发表回复

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