好的,各位靓仔靓女,老少爷们!欢迎来到今天的“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
在这个例子里,quantity 是 0,一个“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,它不是 null 或 undefined,所以 ?? 直接返回了 0。
再看用户名:
let userName = ""; // 用户名为空字符串
let defaultUserName = "Guest";
let finalUserName = userName ?? defaultUserName;
console.log(finalUserName); // 输出 ""
finalUserName 的值是 "",空字符串。?? 同样没有进行替换。
看到了吗??? 只关心是不是 null 或 undefined,其他一概不理。这使得它在处理一些需要区分 0、"" 和 null/undefined 的场景时,更加准确。
第三幕:适用场景大PK
那么,|| 和 ?? 各自适合哪些场景呢?
| 特性/场景 | ` | ` (逻辑或) | ?? (空值合并运算符) |
|
|---|---|---|---|---|
| 判断标准 | 左侧表达式是否为 "falsy" (false, 0, "", null, undefined, NaN) | 左侧表达式是否为 null 或 undefined |
||
| 适用场景 | 只需要一个简单的布尔值,不关心具体的值的场景。 例如:if (variable | someDefaultValue) {…} | 需要区分 0、"" 和 null/undefined 的场景。 需要为变量提供默认值,但只在变量为 null 或 undefined 时才使用默认值。 |
|
| 举例 | enableFeature = userSettings.enableFeature || true; (如果 userSettings.enableFeature 未定义或为 false,则启用功能) |
userName = userSettings.userName ?? "Guest"; (只有当 userSettings.userName 为 null 或 undefined 时,才使用 "Guest" 作为默认用户名) |
||
| 优点 | 兼容性好,所有浏览器都支持。 | 更加精确,避免了不必要的默认值替换。 | ||
| 缺点 | 容易误判,将 0、"" 等 "falsy" 值也当成需要替换的值。 |
兼容性稍差,需要较新的浏览器版本支持。 |
总结一下:
-
用
||的时候: 你不在乎0、""这些值,只要它们是“falsy”的,你就想用默认值。比如,你想确保一个布尔值始终是true,即使原始值是false、0、""、null、undefined或NaN。 -
用
??的时候: 你很在意0、""这些值,它们是有意义的,你只想在变量是null或undefined的时候才用默认值。比如,你想为用户的年龄设置默认值,但如果用户明确输入了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);在这个例子中,
??确保了只有当配置项确实不存在(null或undefined)时,才使用默认值。||则用于处理布尔值的情况。 -
处理用户输入:
假设你正在处理一个表单,用户可以输入一些信息,有些字段是可选的。
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" 值都视为需要替换的值。 - 选择
??的时候,要确保你的目标是只在null或undefined的情况下才使用默认值。
在实际开发中,要根据具体的场景选择合适的操作符,避免出现意想不到的错误。
希望今天的讲座能帮助大家更好地理解 || 和 ?? 的区别。下次再遇到它们,你就可以自信地说:“哼,我早就看穿你们的底细了!”
感谢各位的聆听,下课!