好的,各位靓仔靓女,老少爷们!欢迎来到今天的“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
的情况下才使用默认值。
在实际开发中,要根据具体的场景选择合适的操作符,避免出现意想不到的错误。
希望今天的讲座能帮助大家更好地理解 ||
和 ??
的区别。下次再遇到它们,你就可以自信地说:“哼,我早就看穿你们的底细了!”
感谢各位的聆听,下课!