第48课:函数基础——声明、表达式、箭头函数、参数默认值与剩余参数
函数是 JavaScript 的一等公民(First-Class Citizen)——它们可以被赋值给变量、作为参数传递、作为返回值返回。函数是封装可复用逻辑的基本单元。在 JavaScript 中,创建函数有三种主要方式:函数声明、函数表达式和箭头函数。每种方式在提升行为、this 绑定和语法简洁性上各有差异。理解这些差异,是写出模块化、可测试、可维护代码的前提。
1. 函数声明(Function Declaration)
函数声明是最传统、最直观的函数定义方式。
1 | function greet(name) { |
1.1 函数声明会被提升
函数声明在代码执行前会被 JavaScript 引擎整体提升到所在作用域的顶部。这意味着你可以在声明之前调用它。
1 | console.log(sum(3, 5)); // 8 —— 在声明之前调用,正常工作 |
这种提升行为与 var 变量的提升不同:var 仅提升声明(初始值为 undefined),而函数声明整体提升(声明和定义一起提升)。
1.2 函数声明的作用域
在严格模式(包括 ES Modules 和 class 内部)下,函数声明的作用域是块级的——在 {} 内部声明的函数在外部不可访问。但在非严格模式下,某些浏览器可能将块内函数声明泄漏到外部。
1 | ; |
最佳实践:避免在 if 或 for 等块内部使用函数声明。若需条件性创建函数,使用函数表达式。
2. 函数表达式(Function Expression)
函数表达式将一个函数定义为一个值,并赋值给变量。
1 | const greet = function(name) { |
2.1 函数表达式不会被提升
函数表达式遵循变量声明规则。若用 const 声明,它处于暂时性死区(TDZ),声明前访问会抛出 ReferenceError。
1 | // console.log(add(2, 3)); // ReferenceError: Cannot access 'add' before initialization |
2.2 匿名函数与具名函数表达式
函数表达式右侧的函数可以是匿名的(如上例),也可以是具名的。
1 | const factorial = function fact(n) { |
具名函数表达式的好处:
- 函数内部可以可靠地自引用(用于递归),即使外部变量被重新赋值也不受影响。
- 在错误堆栈中显示函数名,便于调试。
2.3 何时使用函数表达式
| 场景 | 原因 |
|---|---|
条件性创建函数(if 内) |
函数表达式不被提升,条件逻辑清晰。 |
| 将函数作为参数传递(回调) | array.map(function(item) { ... }) |
| 将函数作为对象属性(方法) | { greet: function() { ... } }(方法简写更优) |
| 创建 IIFE(立即执行函数) | (function() { ... })() |
3. 箭头函数(Arrow Function)
箭头函数是 ES6 引入的更简洁的函数语法。它不仅仅是语法糖——在 this 绑定上有本质区别(将在第 49 课闭包中详述)。
3.1 基本语法
1 | // 完整形式 |
3.2 隐式返回与对象字面量的冲突
省略花括号时,箭头函数隐式返回 => 后面的表达式。但如果要返回一个对象字面量,直接用 {} 会被解析为函数体。解决方案是用圆括号包裹对象。
1 | // ❌ 错误:{} 被解析为函数体,代码无效 |
3.3 箭头函数与普通函数的关键区别
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
this 绑定 |
动态:取决于调用方式(谁调用指向谁)。 | 静态:继承定义时外层作用域的 this。 |
arguments 对象 |
有自身的 arguments。 |
没有 arguments(访问到的是外层的)。 |
new 调用 |
可以作为构造函数。 | 不能作为构造函数(会抛出 TypeError)。 |
prototype 属性 |
有 prototype。 |
没有 prototype。 |
super |
有(在类方法中)。 | 没有 super。 |
| 方法简写(对象中) | { fn() {} } 是普通函数。 |
{ fn: () => {} } 是箭头函数。 |
1 | // arguments 的差异 |
箭头函数的 this 将在第 49 课结合执行上下文深入讲解,这里先记住核心规则:箭头函数中的 this 是**它所在词法作用域的 this**,在函数创建时就已经确定,不会因调用方式而改变。
3.4 何时不该用箭头函数
- 对象的方法(特别是需要访问对象自身属性的场景):箭头函数无法通过
this访问对象。 - 事件监听器(当回调需要
this指向触发事件的元素时):使用普通函数以获取event.currentTarget。 - 构造函数:箭头函数不能与
new一起使用。 - 需要动态
this的场景:如使用call、apply、bind显式绑定this时,箭头函数不受影响。
4. 函数参数:默认值与剩余参数
ES6 为函数参数带来了两项重要增强:默认参数值和剩余参数。它们大幅减少了手动参数校验的样板代码。
4.1 默认参数值
可以为任何参数指定默认值。当对应参数未传入或显式传入 undefined 时,使用默认值。
1 | function createUser(name, role = 'user', isActive = true) { |
关键规则:
- 只有
undefined会触发默认值。null、false、0、''等都是有效参数值,不会被替换。 - 默认值表达式在函数调用时惰性求值,而非定义时。
- 后面的参数可以使用前面参数的值作为默认值。
1 | function createId(prefix, id = prefix + '_' + Math.random().toString(36).slice(2)) { |
4.2 剩余参数 ...
剩余参数语法(...paramName)将函数调用时多余的所有参数收集到一个真正的数组中。它替代了 ES5 中类数组的 arguments 对象。
1 | function sum(...numbers) { |
与 arguments 对象的对比:
| 特性 | arguments |
剩余参数 ...args |
|---|---|---|
| 类型 | 类数组对象(有 length,无数组方法)。 |
真正的数组。 |
| 箭头函数中 | 不存在(访问的是外层 arguments)。 |
正常工作。 |
| 是否包含前面具名参数 | 包含所有参数。 | 仅包含未被具名参数接收的部分。 |
| 可遍历性 | 需要 Array.from(arguments) 转换。 |
可直接使用 map、filter、reduce。 |
1 | function demo(a, b, ...rest) { |
使用约束:
- 剩余参数必须是函数声明的最后一个参数。
- 一个函数只能有一个剩余参数。
- 剩余参数在
Function.length中不计入(length是函数期望的具名参数数量)。
1 | function test(...args) {} |
5. 综合示例:三种函数类型的实际应用
1 | // 1. 函数声明:适合顶层工具函数(有提升,可在调用前定义) |
课后练习
一、概念自测(选择题 / 填空题)
(单选) 关于函数声明的提升,以下描述正确的是?
A. 函数声明不会提升,必须在调用前定义。
B. 函数声明的声明部分提升,函数体不提升。
C. 函数声明整体提升,可以在声明前调用。
D. 仅箭头函数会提升。(单选) 以下关于箭头函数的描述,哪项是错误的?
A. 箭头函数不能作为构造函数使用new。
B. 箭头函数内部没有自己的this。
C. 箭头函数内部没有arguments对象。
D. 箭头函数的this取决于调用方式。(填空) 剩余参数语法使用
______前缀,将多余的传入参数收集到一个真正的数组中。(多选) 以下哪些场景中,调用
func(undefined)会触发参数的默认值?
A.function func(x = 10) {}
B.function func(x = 10, y = x * 2) {}
C.function func(x) {}
D.function func(x = 10) {}调用func(null)
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要编写一个 JavaScript 工具模块,包含以下函数:
createCounter(initialValue = 0):使用函数表达式返回一个对象,包含increment、decrement和getValue三个方法(均用箭头函数定义,以演示箭头函数可以捕获外层this或不依赖this的场景)。average(...numbers):使用箭头函数声明,接收任意数量的数字,返回它们的平均值。如果未传入任何数字,返回0。formatName(firstName, lastName, title = ''):使用函数声明,返回格式化后的全名,如果提供了title,则放在名字前面(如'Dr. Alice Smith')。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 JavaScript 代码。提示词中需明确指定每种函数的定义方式(声明/表达式/箭头)、参数默认值、剩余参数的使用,以及代码注释。
课后练习答案
一、概念自测答案
C
- 解析:函数声明整体提升,包含函数名和函数体,因此可以在声明之前调用。函数表达式(包括箭头函数)遵循变量提升规则,不会整体提升。
D
- 解析:箭头函数的
this是静态绑定的,取决于定义时外层作用域的this,而非调用方式。D 描述的是普通函数的行为。
- 解析:箭头函数的
**
...**(三个点)- 解析:
...paramName将剩余参数收集到一个数组中。
- 解析:
A、B
- 解析:默认值仅在参数为
undefined时触发。A 中传undefined触发默认值10;B 中传undefined也触发默认值。C 没有默认值;D 中传null不会触发默认值(null是有效值)。
- 解析:默认值仅在参数为
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请编写一个 JavaScript 工具模块,导出三个函数。要求:
createCounter(initialValue = 0):使用const createCounter = function(initialValue = 0) { ... }函数表达式定义。返回一个对象,包含increment()(将内部计数 +1 并返回新值)、decrement()(-1)、getValue()(返回当前值)。三个方法使用箭头函数定义(以演示语法,即使此处不依赖this)。average:使用const average = (...numbers) => { ... }箭头函数定义。若numbers.length === 0返回0,否则返回numbers.reduce((sum, n) => sum + n, 0) / numbers.length。formatName(firstName, lastName, title = ''):使用函数声明function formatName(...)定义。若title不为空字符串,返回${title} ${firstName} ${lastName},否则返回${firstName} ${lastName}。- 每个函数添加 JSDoc 注释说明参数和返回值。
- 代码简洁,遵循命名规范。输出完整代码。”