A05 函数与参数
本文讲解JavaScript函数定义方式、参数设计规则与返回值约定,帮助开发者写出契约清晰、可测试的函数。
- 函数调用经历建立上下文、绑定参数、执行体、返回结果四个阶段,参数不足不报错。
- 四种函数定义方式各有适用场景,箭头函数继承外层this,适合回调与工具函数。
- 参数超过三个改用对象参数,可选参数给默认值,非法输入在入口立即抛错。
- 默认值只对undefined生效,传null不会触发默认值,需调用侧用??兜底。
- 核心逻辑写成纯函数,函数不修改入参,副作用留在边界处理。
A05 函数与参数
摘要:函数把输入映射为输出,是复用与测试的最小单位。参数传递只有一种语义:原始类型传值,对象传引用;默认参数、剩余参数与解构能显著减少调用侧的样板代码。本文给出函数声明与表达式的取舍、参数设计规则、返回值约定与纯函数判定方法。
关键词:函数,参数,默认值,剩余参数,纯函数
所属:frontend/js / A-入门 | 上一篇:A04-条件分支与循环 | 下一篇:A06-数组与对象
1 引言
同一个功能写成三个函数时,缺陷通常不在语法而在契约:参数是必填还是可选、返回值是原始值还是对象、出错时返回 null 还是抛异常。契约不清晰,调用方只能读实现。
本文解决三个问题:函数怎么写、参数怎么设计、返回值怎么约定。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 定义 | 声明、表达式、箭头函数、立即执行 | 构造函数与 new(B02) |
| 参数 | 默认值、剩余参数、解构、对象参数 | 柯里化与函数组合(B01) |
| 返回值 | 单一出口、早返回、纯函数判定 | 异步函数(B03) |
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | Node.js 20+ 或浏览器控制台 | 能执行多行脚本 |
| 前置 | A04 | 能写分支与循环 |
| 文件 | js-practice/functions.js |
编码 UTF-8 |
3 原理与实现
3.1 调用时的四项动作
一次调用按「建立执行上下文 → 绑定参数 → 执行函数体 → 返回结果」进行,如图 3-1 所示。
flowchart TD
A["调用 fn(a, b)"] --> B["创建新的执行上下文"]
B --> C["形参绑定:缺省用默认值,多余进 rest"]
C --> D["执行函数体"]
D --> E{"遇到 return"}
E -->|是| F["返回该值并结束"]
E -->|无 return| G["返回 undefined"]
F --> H["调用方继续"]
G --> H
图 3-1 一次函数调用的四个阶段
图上可见:参数不足不会报错,未传的形参为
undefined;只有显式return才有返回值,漏写在链式调用中会得到undefined。
3.2 完整实现
// js-practice/functions.js;运行环境:Node.js 20+
// 1 对象参数:超过两个参数时改用解构,调用侧可读
function createCard({ title, price = 0, tags = [] } = {}) {
if (!title) throw new TypeError('title 必填');
return { title, price, tags, label: `${title} · ${price} 元` };
}
// 2 默认值只在 undefined 时生效
const withDefault = (n = 10) => n;
console.log(withDefault(undefined), withDefault(null)); // 10 null
// 3 剩余参数收集多余实参
const sum = (...nums) => nums.reduce((acc, n) => acc + n, 0);
console.log(sum(1, 2, 3)); // 6
// 4 纯函数:同输入同输出,无副作用
const round = (n, digits = 2) => Number(n.toFixed(digits));
console.log(round(3.14159), round(3.14159)); // 3.14 3.14
// 5 有副作用时把副作用留在边界,核心逻辑保持纯函数
function applyDiscount(cart, rate) {
const total = cart.reduce((acc, i) => acc + i.price * i.qty, 0);
return { ...cart, total: round(total * (1 - rate)) };
}
const cart = [{ price: 19.9, qty: 2 }, { price: 5, qty: 1 }];
console.log(applyDiscount(cart, 0.1)); // total: 40.32
console.log(cart); // 原数组未被修改
// 6 立即执行函数:只在需要局部作用域时使用
const version = (() => {
const tag = 'v1';
return tag;
})();
console.log(version); // v1
四种定义方式的差异见表 3-1。
| 方式 | 写法 | this 绑定 |
适用场景 |
|---|---|---|---|
| 函数声明 | function f() {} |
调用时决定 | 需要提升、需要 this |
| 函数表达式 | const f = function () {} |
调用时决定 | 条件性定义 |
| 箭头函数 | const f = () => {} |
继承外层 | 回调、工具函数 |
| 对象方法简写 | { f() {} } |
调用者 | 对象行为 |
参数设计规则见表 3-2。
| 规则 | 做法 | 原因 |
|---|---|---|
| 参数不超过三个 | 超出改用对象参数 | 调用侧无法记住顺序 |
| 可选参数给默认值 | price = 0 |
避免函数体内散布判空 |
| 非法输入立即抛错 | 入口 throw new TypeError |
错误在最近的位置暴露 |
| 不修改传入对象 | 返回新对象或显式命名 mutateXxx |
调用方难以预期副作用 |
4 验证与讨论
- 执行脚本,确认输出
6、3.14 3.14、total: 40.32。 - 调用
createCard({}),确认抛出title 必填,且错误信息指明参数名。 - 把
round改为直接修改外部变量,观察同一输入两次输出不同,理解纯函数的判定标准。 - 用
console.log(cart)确认applyDiscount未改变原数组。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 默认值边界 | 传 null 得到 null |
默认值只对 undefined 生效 |
| 剩余参数 | sum(1,2,3) 为 6 |
以数组形式收集 |
| 纯函数 | 两次调用结果一致 | 无外部状态依赖 |
| 不可变返回 | 原 cart 未变化 |
展开生成新对象 |
讨论与局限。 箭头函数没有自己的 arguments 与 this,不能用作构造函数,也不能在需要动态 this 的对象方法中替换普通函数,详见 B02。另,默认值为对象字面量时每次调用都会重新创建,适合做兜底但不适合当缓存。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 默认值不生效 | 传了 null 而非 undefined |
调用侧用 ?? 兜底,或函数内判空 |
| 修改入参导致他处出错 | 函数内直接改对象属性 | 展开复制后再改 |
arguments 在箭头函数中报错 |
箭头函数无 arguments |
改用剩余参数 |
返回值总是 undefined |
函数体漏写 return |
检查每个分支是否返回 |
回调里 this 不是预期对象 |
普通函数作为回调 | 改用箭头函数或 bind |
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | 参数超过三个改用对象参数 | 表 3-2 |
| 2 | 默认值只对 undefined 生效 |
3.2 节 |
| 3 | 核心逻辑写成纯函数,副作用留在边界 | 图 3-1 |
| 4 | 函数不修改入参,除非命名显式声明 | 表 3-2 |
- 上一篇:A04-条件分支与循环
- 下一篇:A06-数组与对象
- 返回:A00-总结 | 科目 README
参考文献
[1] MDN Web Docs · 函数,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Functions(访问于 2026-10-31)
[2] ECMA-262 · 10 Ordinary and Exotic Objects Behaviours,https://tc39.es/ecma262/#sec-ordinary-and-exotic-objects-behaviours(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~