宜宾市网站建设_网站建设公司_Oracle_seo优化
2026/1/9 21:36:39 网站建设 项目流程

如何用??写出更聪明的函数:别再让0被误判成“空值”了

你有没有遇到过这种场景?

function setVolume(level) { const volume = level || 10; // 默认音量为 10 console.log(`音量设置为:${volume}`); }

调用时传了个0

setVolume(0); // 输出:音量设置为:10 ❌

结果音量没关上,反而变成了默认值。用户明明想静音,却被当成“无效输入”,直接兜底了。

这问题的根本原因,就是用了||来处理默认值。而现代 JavaScript 早已提供了更精准的替代方案 ——空值合并操作符??

它只在值真正“不存在”(即nullundefined)时才启用默认值,对0''false等合法但“假”的值完全无感。这才是我们想要的安全默认逻辑。


为什么||不适合做默认值?

在 JS 中,有六个“假值”(falsy values):

  • false
  • 0
  • ''(空字符串)
  • null
  • undefined
  • NaN

||操作符会把这些统统视为“无效”,一旦左边是其中之一,就返回右边。

console.log(0 || 10); // 10 console.log('' || 'abc'); // 'abc' console.log(false || true); // true

但在业务中,这些往往是有效输入。比如:

  • 表单允许填写年龄0岁(婴儿)
  • 配置项可以关闭某个功能开关(false
  • 用户名可以叫"0"或空字符串(特殊账号)

这时候如果还用||,就会把用户的明确意图给“吃掉”。

🚨 关键点:我们真正关心的不是“是不是假值”,而是“这个参数到底有没有传进来”。

这就是??存在的意义。


??的核心行为:只认nullundefined

??只在这两种情况下才会取默认值:

null ?? 'default' // → 'default' undefined ?? 'default' // → 'default' // 其他所有情况都保留原值 0 ?? 10 // → 0 ✅ '' ?? 'abc' // → '' ✅ false ?? true // → false ✅ [] ?? [1] // → [] ✅ {} ?? {x:1} // → {} ✅

它的判断逻辑非常清晰:

left ?? right // 等价于 left != null ? left : right // 注意这里用的是 !=,不是 !==,所以能同时捕获 null 和 undefined

而且它是短路求值的 —— 如果左侧不是 nullish,右侧根本不会执行。这意味着你可以放心写副作用表达式:

const timeout = config.timeout ?? startTimer(); // 只有 config.timeout 缺失时才会启动计时器

ES6 函数扩展 +??:组合拳才是王道

ES6 给我们带来了强大的函数参数能力,尤其是解构赋值默认参数。但它们和??各有分工,搞清楚谁该干啥,才能写出干净代码。

默认参数只能救undefined

先看一个常见误区:

function greet(name = 'Guest') { return `Hello, ${name}`; } greet(); // Hello, Guest ✅ greet(undefined); // Hello, Guest ✅ greet(null); // Hello, null ❌ greet(''); // Hello, ✅(虽然空,但合法)

看到了吗?默认参数只对undefined生效,对null完全无效

所以如果你接收的是 API 返回的数据,字段可能是显式设为null的,那默认参数就失效了。

解决方案?在函数体内补上??

function greet(name) { name = name ?? 'Guest'; // 同时处理 null 和 undefined return `Hello, ${name}`; }

或者更简洁地:

function greet(name) { const finalName = name ?? 'Guest'; return `Hello, ${finalName}`; }

这样无论传null还是undefined,都能得到合理回退。


解构参数 +??:配置对象的最佳拍档

现代函数越来越多使用“选项对象”作为参数,比如:

function createServer(options) { const port = options.port ?? 3000; const host = options.host ?? 'localhost'; const ssl = options.ssl ?? true; // ... }

我们可以直接用解构来简化:

function createServer({ port, host, ssl } = {}) { // 注意:= {} 是为了防止调用时不传任何参数导致解构报错 const finalPort = port ?? 3000; const finalHost = host ?? 'localhost'; const finalSsl = ssl ?? true; console.log(`Starting server on ${finalHost}:${finalPort}`); }

重点来了:为什么不直接写成:

function createServer({ port = 3000, host = 'localhost' }) { ... }

因为默认参数在这里依然只响应undefined!如果调用方写了:

createServer({ port: null });

那么port就是null,不会触发默认值。而我们通常希望null也代表“我不指定”,这时就必须靠??来兜底。

💡 小技巧:解构时可以用??在变量赋值后立刻处理,形成统一模式。


实战案例:构建一个安全的配置合并工具

设想你要写一个插件系统,允许用户传入部分配置,其余用默认值填充。

const defaultConfig = { timeout: 5000, retry: 3, mode: 'strict', debug: false, };

错误做法(用||):

function mergeConfig(user) { const result = {}; for (let key in defaultConfig) { result[key] = user[key] || defaultConfig[key]; } return result; } mergeConfig({ timeout: 0, debug: false }); // → { timeout: 5000, retry: 3, mode: 'strict', debug: false } // ❌ 0 被替换了!用户本意是设超时为 0(立即失败),却被改成 5s

正确做法(用??):

function mergeConfig(user) { const result = {}; for (let key in defaultConfig) { result[key] = user?.[key] ?? defaultConfig[key]; } return result; } mergeConfig({ timeout: 0, debug: false }); // → { timeout: 0, retry: 3, mode: 'strict', debug: false } ✅

甚至支持null显式清除某个配置:

mergeConfig({ retry: null }); // → { timeout: 5000, retry: null, mode: 'strict', debug: false } // 下游可以根据 retry === null 判断是否禁用重试机制

这才叫真正的“尊重用户输入”。


常见坑点与最佳实践

⚠️ 坑一:混淆||??的语义

场景推荐操作符
判断“是否有数据”??
判断“是否开启功能”||

例如:

// ✅ 数据存在性检查 → 用 ?? const id = response.data.id ?? generateTempId(); // ✅ 功能开关 → 用 || const darkMode = userPrefs.darkMode || systemTheme.darkMode;

记住一句话:

??是“缺了才补”,||是“假了就换”

根据业务意图选择,不要无脑替换。


⚠️ 坑二:操作符优先级陷阱

??的优先级低于&&||,不能混用而不加括号:

// ❌ 错误!语法不允许 a && b ?? c; // ✅ 必须加括号 (a && b) ?? c; a && (b ?? c);

V8 引擎会直接抛出语法错误,提醒你明确意图。


✅ 最佳实践清单

  1. 默认值处理优先用??
    js value ?? defaultValue

  2. 解构后立即??
    js function render({ width, height }) { const w = width ?? 800; const h = height ?? 600; }

  3. 避免在默认参数里嵌套??
    ```js
    // ❌ 可读性差
    function f(x = y ?? z) {}

// ✅ 更清晰
function f(x) {
const actual = x ?? y ?? z;
}
```

  1. 配合可选链一起用
    js const city = user?.address?.city ?? 'Unknown';

  2. 注意兼容性
    -??不支持 IE
    - 使用 Babel 编译(@babel/plugin-proposal-nullish-coalescing-operator)
    - 或 TypeScript 设置目标为 ES2020+


结语:从“能跑”到“靠谱”

JavaScript 的进化,不只是语法糖的堆砌,更是思维方式的升级。

过去我们用||是图省事,但现在有了??,我们就应该追求更精确的控制。

当你写的函数既能正确处理0,又能区分nullundefined,还能优雅合并配置项时,你的代码就已经超越了“能跑就行”的阶段,进入了“值得信赖”的领域。

下次写函数时,不妨多问一句:

“我这里的默认逻辑,真的需要排斥0''吗?”

如果不是,请果断换上??。小小的符号改变,可能避免未来某个深夜的线上事故。

如果你正在重构旧项目,搜索一下代码中的||,看看哪些其实是该用??的地方 —— 这可能是性价比最高的技术债清理之一。

欢迎在评论区分享你踩过的“默认值陷阱”,我们一起避坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询