jstips 第 12 期:ES6 伪强制参数(Pseudomandatory Parameters)——用默认参数实现必传校验

原创2026-10-07 09:01:131,759 阅读
文章标签:教程

jstips 第 12 期:ES6 伪强制参数(Pseudomandatory Parameters)——用默认参数实现必传校验

导读

在 JavaScript 中,函数的每一个参数天然都是可选的,调用方少传参数不会报错,只会得到 undefined。本文基于 jstips 仓库第 12 期技巧(见 _posts/zh_CN/javascript/2016-01-12-pseudomandatory-parameters-in-es6-functions.md,英文原版见 _posts/en/javascript/2016-01-12-pseudomandatory-parameters-in-es6-functions.md),讲解如何借助 ES6 的参数默认值(default parameter)特性,在完全不污染函数体(不写任何 if 判断)的前提下,让参数变得"伪强制"(pseudomandatory):漏传参数时立即抛出带有明确信息的 Error。读完本篇,你将掌握这一模式的原理、边界行为,以及它在日常 API 设计与参数校验中的实战用法。

背景:为什么需要"伪强制参数"

在许多编程语言中(如 Java、C#、Python 的部分场景),函数的参数默认是强制必传的,开发者需要显式声明某个参数为可选。而 JavaScript 恰恰相反:每一个参数都是可选的——调用时不传的参数会被赋值为 undefined,函数照常执行,不会抛出任何错误。

这种"宽容"在小型脚本中很便利,但在大型代码库或作为公共 API 对外暴露时,漏传关键参数往往会导致难以排查的 TypeError 或静默的错误结果。传统做法是在函数体内手动检查:

function getSum(a, b) {
  if (a === undefined) {
    throw new Error('a is not defined');
  }
  if (b === undefined) {
    throw new Error('b is not defined');
  }
  return a + b;
}

这种做法的问题在于:校验逻辑与业务逻辑混杂在函数体中,每增加一个参数就要多写一段判断,函数体被不断"污染"。

核心方案:默认参数 + 抛错函数的组合

ES6 的参数默认值特性允许为参数指定默认值——当参数为 undefined 时(未传入或显式传入 undefined),默认值表达式会被求值。jstips 第 12 期的思路正是利用这一点:把默认值设为一个"立刻抛错"的函数调用,让缺失参数自动触发异常。

原文档给出的完整示例:

const _err = function( message ){

  throw new Error( message );

}

const getSum = (a = _err('a is not defined'), b = _err('b is not defined')) => a + b

getSum( 10 ) // throws Error, b is not defined

getSum( undefined, 10 ) // throws Error, a is not defined

逐行拆解

  • _err 是一个立即抛出 Error 的工具函数,唯一职责是构造并抛出异常;
  • getSum 是一个箭头函数(关于箭头函数可参考 jstips 第 14 期 _posts/en/javascript/2016-01-14-fat-arrow-functions.md),它的两个形参 a、b 都设置了默认值,而默认值表达式就是 _err(...) 的调用;
  • 当调用 getSum(10) 时,a 正常接收到 10,而 b 为 undefined,于是 b 的默认值表达式 _err('b is not defined') 被求值,抛出 Error: b is not defined;
  • 当调用 getSum(undefined, 10) 时,a 显式传入了 undefined——注意,显式传 undefined 同样会触发默认值——于是抛出 Error: a is not defined。

这套组合的意义在于:函数体本身零校验代码,参数契约直接写在签名里,任何阅读签名的人一眼就能看出哪些参数是必须的。

深入原理:默认参数的求值时机与触发条件

要正确使用这个模式,需要精确理解 ES6 默认参数的几条行为规则:

1. 只有 undefined 会触发默认值

默认值仅在参数值为 undefined 时生效,触发场景包括:

  • 调用时完全未传该位置参数;
  • 调用时显式传入 undefined(如上例 getSum(undefined, 10))。

而 null 不会触发默认值。若调用 getSum(null, 10),a 会拿到 null,_err 不会被调用,随后 null + 10 计算得到 10,校验被静默绕过。这与 jstips 第 5 期 _posts/zh_CN/javascript/2016-01-05-differences-between-undefined-and-null.md 中强调的 undefined 与 null 语义差异一脉相承:在需要"必传校验"的场景,应引导调用方用 undefined 表达"未提供",而不是 null。

2. 默认值表达式是惰性求值的

默认值表达式(如 _err('a is not defined'))只在参数缺失时才会被求值,且是每次调用时动态求值。也就是说,参数齐全的正常调用不会产生任何 _err 调用的开销,校验成本为零。

3. 默认值可以引用前面的参数

默认值表达式中可以引用同参数列表中更靠前的参数,这让"第二个参数必传,且依赖第一个参数"这类更复杂的约束成为可能。

边界情况与模式变体

变体一:普通函数同样适用

原文档示例用的是箭头函数,但默认参数特性对 function 声明的普通函数、方法、构造函数完全通用:

const _err = (message) => { throw new Error(message); };

function divide(dividend = _err('dividend is not defined'),
                divisor = _err('divisor is not defined')) {
  return dividend / divisor;
}

甚至可以把 _err 定义成更通用的"必需参数"标记:

const required = (name) => { throw new Error(`Parameter "${name}" is required`); };

function connect(url = required('url'), timeout = 1000) {
  // url 必传,timeout 可选,默认 1000
  return { url, timeout };
}

这种写法将"哪些参数必传、哪些可选、可选的默认值是什么"完整地暴露在签名上,可读性极佳。

变体二:与解构默认参数组合

当函数接收的是一个配置对象时,可以将"伪强制"思想与解构默认参数结合——jstips 第 43 期 _posts/en/javascript/2016-02-12-use-destructuring-in-function-parameters.md 详细讲解了参数解构与默认参数的搭配。例如要求 options 对象中必须包含 token 字段:

function request({ token = required('token'), url = required('url') } = {}) {
  // 缺少 token 或 url 时抛出对应错误
  return { token, url };
}

request({ token: 'abc' });      // Error: Parameter "url" is required
request();                      // Error: Parameter "token" is required

注意 = {} 兜底:当整个实参对象缺失时,先回退到 {} 再解构,此时属性默认值(即 required(...) 调用)才会触发,从而抛出精确的错误信息——这正是第 43 期文档中 = {} 兜底场景与本期"伪强制"思想的结合点。

边界提示:默认值不会拦截 null

再次强调:若调用方传入 null(如 request({ token: null, url: 'x' })),属性默认值同样不会触发。对于严格要求"非空"的场景,可在 required 之外再配合显式校验,或者约定 API 语义为"必须使用 undefined 表示缺失"。

在仓库中的位置与延伸阅读

本篇技巧是 jstips 仓库第 12 期(tip-number: 12),在项目 README.md 的 Tips 列表中以 "Pseudomandatory parameters in ES6 functions" 登记,并提供了中文(zh_CN)、繁体中文(zh_TW)、西班牙语(es_ES)等多个语言版本(分别位于 _posts/zh_TW/javascript/2016-01-12-pseudomandatory-parameters-in-es6-functions.md 与 _posts/es_ES/javascript/2016-01-12-pseudomandatory-parameters-in-es6-functions.md),可用于对照学习。

与本期主题相关的仓库内延伸阅读:

小结

"伪强制参数"模式用一句话概括:把默认参数当作钩子,钩住一个必然抛错的函数。它的核心价值在于:

  1. 参数契约显式化——必传参数一眼可见;
  2. 函数体零污染——不写任何手工校验分支;
  3. 零性能损耗——默认值惰性求值,正常调用不触发。

使用时只需牢记两个前提:默认值只在参数为 undefined 时触发,因此调用方应以 undefined 而非 null 表示"未提供";若 API 需要更复杂的约束(如类型校验、非空校验),仍需要在此基础上叠加其他校验手段。这个模式非常适合库函数、公共 API 的参数契约声明,是 ES6 默认参数特性最实用的落地场景之一。

登录后查看全文
jstips