WinddSnow

JavaScript-Functions-Basics-Declarations-Expressions-Arrow-Functions

字数统计: 3.2k阅读时长: 12 min
2026/07/30

第48课:函数基础——声明、表达式、箭头函数、参数默认值与剩余参数

函数是 JavaScript 的一等公民(First-Class Citizen)——它们可以被赋值给变量、作为参数传递、作为返回值返回。函数是封装可复用逻辑的基本单元。在 JavaScript 中,创建函数有三种主要方式:函数声明函数表达式箭头函数。每种方式在提升行为、this 绑定和语法简洁性上各有差异。理解这些差异,是写出模块化、可测试、可维护代码的前提。


1. 函数声明(Function Declaration)

函数声明是最传统、最直观的函数定义方式。

1
2
3
4
5
function greet(name) {
return `Hello, ${name}!`;
}

console.log(greet('Alice')); // 'Hello, Alice!'

1.1 函数声明会被提升

函数声明在代码执行前会被 JavaScript 引擎整体提升到所在作用域的顶部。这意味着你可以在声明之前调用它。

1
2
3
4
5
console.log(sum(3, 5)); // 8 —— 在声明之前调用,正常工作

function sum(a, b) {
return a + b;
}

这种提升行为与 var 变量的提升不同:var 仅提升声明(初始值为 undefined),而函数声明整体提升(声明和定义一起提升)。

1.2 函数声明的作用域

在严格模式(包括 ES Modules 和 class 内部)下,函数声明的作用域是块级的——在 {} 内部声明的函数在外部不可访问。但在非严格模式下,某些浏览器可能将块内函数声明泄漏到外部。

1
2
3
4
5
'use strict';
{
function blockFunc() { return 'inside'; }
}
console.log(typeof blockFunc); // 'undefined' —— 严格模式下块级作用域

最佳实践:避免在 iffor 等块内部使用函数声明。若需条件性创建函数,使用函数表达式。


2. 函数表达式(Function Expression)

函数表达式将一个函数定义为一个,并赋值给变量。

1
2
3
4
5
const greet = function(name) {
return `Hello, ${name}!`;
};

console.log(greet('Bob')); // 'Hello, Bob!'

2.1 函数表达式不会被提升

函数表达式遵循变量声明规则。若用 const 声明,它处于暂时性死区(TDZ),声明前访问会抛出 ReferenceError

1
2
3
4
5
6
7
// console.log(add(2, 3)); // ReferenceError: Cannot access 'add' before initialization

const add = function(a, b) {
return a + b;
};

console.log(add(2, 3)); // 5

2.2 匿名函数与具名函数表达式

函数表达式右侧的函数可以是匿名的(如上例),也可以是具名的。

1
2
3
4
5
6
7
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // 内部可以用函数名 fact 递归调用
};

console.log(factorial(5)); // 120
// console.log(fact(5)); // ReferenceError: fact is not defined(外部不可见)

具名函数表达式的好处

  • 函数内部可以可靠地自引用(用于递归),即使外部变量被重新赋值也不受影响。
  • 在错误堆栈中显示函数名,便于调试。

2.3 何时使用函数表达式

场景 原因
条件性创建函数(if 内) 函数表达式不被提升,条件逻辑清晰。
将函数作为参数传递(回调) array.map(function(item) { ... })
将函数作为对象属性(方法) { greet: function() { ... } }(方法简写更优)
创建 IIFE(立即执行函数) (function() { ... })()

3. 箭头函数(Arrow Function)

箭头函数是 ES6 引入的更简洁的函数语法。它不仅仅是语法糖——在 this 绑定上有本质区别(将在第 49 课闭包中详述)。

3.1 基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 完整形式
const multiply = (a, b) => {
const result = a * b;
return result;
};

// 仅一条 return 语句:省略花括号和 return
const add = (a, b) => a + b;

// 仅一个参数:省略圆括号
const square = x => x * x;

// 无参数:必须保留空圆括号
const greet = () => 'Hello!';

3.2 隐式返回与对象字面量的冲突

省略花括号时,箭头函数隐式返回 => 后面的表达式。但如果要返回一个对象字面量,直接用 {} 会被解析为函数体。解决方案是用圆括号包裹对象

1
2
3
4
5
6
7
// ❌ 错误:{} 被解析为函数体,代码无效
// const user = (name, age) => { name, age };

// ✅ 正确:用 () 包裹对象字面量
const createUser = (name, age) => ({ name, age });

console.log(createUser('Alice', 25)); // { name: 'Alice', age: 25 }

3.3 箭头函数与普通函数的关键区别

特性 普通函数 箭头函数
this 绑定 动态:取决于调用方式(谁调用指向谁)。 静态:继承定义时外层作用域的 this
arguments 对象 有自身的 arguments 没有 arguments(访问到的是外层的)。
new 调用 可以作为构造函数。 不能作为构造函数(会抛出 TypeError)。
prototype 属性 prototype 没有 prototype
super 有(在类方法中)。 没有 super
方法简写(对象中) { fn() {} } 是普通函数。 { fn: () => {} } 是箭头函数。
1
2
3
4
5
6
7
8
9
10
// arguments 的差异
function regularFunc() {
console.log(arguments); // [1, 2, 3] —— 函数自身的 arguments
}

const arrowFunc = () => {
// console.log(arguments); // ReferenceError: arguments is not defined
};

regularFunc(1, 2, 3);

箭头函数的 this 将在第 49 课结合执行上下文深入讲解,这里先记住核心规则:箭头函数中的 this 是**它所在词法作用域的 this**,在函数创建时就已经确定,不会因调用方式而改变。

3.4 何时不该用箭头函数

  • 对象的方法(特别是需要访问对象自身属性的场景):箭头函数无法通过 this 访问对象。
  • 事件监听器(当回调需要 this 指向触发事件的元素时):使用普通函数以获取 event.currentTarget
  • 构造函数:箭头函数不能与 new 一起使用。
  • 需要动态 this 的场景:如使用 callapplybind 显式绑定 this 时,箭头函数不受影响。

4. 函数参数:默认值与剩余参数

ES6 为函数参数带来了两项重要增强:默认参数值剩余参数。它们大幅减少了手动参数校验的样板代码。

4.1 默认参数值

可以为任何参数指定默认值。当对应参数未传入或显式传入 undefined 时,使用默认值。

1
2
3
4
5
6
7
function createUser(name, role = 'user', isActive = true) {
return { name, role, isActive };
}

console.log(createUser('Alice')); // { name: 'Alice', role: 'user', isActive: true }
console.log(createUser('Bob', 'admin')); // { name: 'Bob', role: 'admin', isActive: true }
console.log(createUser('Charlie', undefined, false)); // { name: 'Charlie', role: 'user', isActive: false }

关键规则

  • 只有 undefined 会触发默认值。nullfalse0'' 等都是有效参数值,不会被替换。
  • 默认值表达式在函数调用时惰性求值,而非定义时。
  • 后面的参数可以使用前面参数的值作为默认值。
1
2
3
4
function createId(prefix, id = prefix + '_' + Math.random().toString(36).slice(2)) {
return id;
}
console.log(createId('user')); // 'user_k9x2m1a...'

4.2 剩余参数 ...

剩余参数语法(...paramName)将函数调用时多余的所有参数收集到一个真正的数组中。它替代了 ES5 中类数组的 arguments 对象。

1
2
3
4
5
6
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(10, 20)); // 30

arguments 对象的对比

特性 arguments 剩余参数 ...args
类型 类数组对象(有 length,无数组方法)。 真正的数组
箭头函数中 不存在(访问的是外层 arguments)。 正常工作。
是否包含前面具名参数 包含所有参数 仅包含未被具名参数接收的部分
可遍历性 需要 Array.from(arguments) 转换。 可直接使用 mapfilterreduce
1
2
3
4
5
6
function demo(a, b, ...rest) {
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5] —— 仅剩余参数
}
demo(1, 2, 3, 4, 5);

使用约束

  • 剩余参数必须是函数声明的最后一个参数
  • 一个函数只能有一个剩余参数。
  • 剩余参数在 Function.length 中不计入(length 是函数期望的具名参数数量)。
1
2
function test(...args) {}
console.log(test.length); // 0 —— 剩余参数不计入 length

5. 综合示例:三种函数类型的实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 函数声明:适合顶层工具函数(有提升,可在调用前定义)
function formatCurrency(amount, currency = 'CNY') {
const symbols = { CNY: '¥', USD: '$', EUR: '€' };
return `${symbols[currency]}${amount.toFixed(2)}`;
}

// 2. 函数表达式:适合条件性创建或作为回调
const applyDiscount = function(price, discount = 0.1) {
if (discount < 0 || discount > 1) {
throw new Error('折扣必须在 0 到 1 之间');
}
return price * (1 - discount);
};

// 3. 箭头函数:适合短小的纯函数和数组方法回调
const prices = [100, 200, 300];
const discountedPrices = prices.map(price => applyDiscount(price, 0.15));

console.log(formatCurrency(discountedPrices[0], 'CNY')); // '¥85.00'

// 4. 剩余参数:处理不定数量输入
const calculateAverage = (...numbers) => {
if (numbers.length === 0) return 0;
return numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
};

console.log(calculateAverage(10, 20, 30, 40)); // 25

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) 关于函数声明的提升,以下描述正确的是?
    A. 函数声明不会提升,必须在调用前定义。
    B. 函数声明的声明部分提升,函数体不提升。
    C. 函数声明整体提升,可以在声明前调用。
    D. 仅箭头函数会提升。

  2. (单选) 以下关于箭头函数的描述,哪项是错误的?
    A. 箭头函数不能作为构造函数使用 new
    B. 箭头函数内部没有自己的 this
    C. 箭头函数内部没有 arguments 对象。
    D. 箭头函数的 this 取决于调用方式。

  3. (填空) 剩余参数语法使用 ______ 前缀,将多余的传入参数收集到一个真正的数组中。

  4. (多选) 以下哪些场景中,调用 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):使用函数表达式返回一个对象,包含 incrementdecrementgetValue 三个方法(均用箭头函数定义,以演示箭头函数可以捕获外层 this 或不依赖 this 的场景)。
  • average(...numbers):使用箭头函数声明,接收任意数量的数字,返回它们的平均值。如果未传入任何数字,返回 0
  • formatName(firstName, lastName, title = ''):使用函数声明,返回格式化后的全名,如果提供了 title,则放在名字前面(如 'Dr. Alice Smith')。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 JavaScript 代码。提示词中需明确指定每种函数的定义方式(声明/表达式/箭头)、参数默认值、剩余参数的使用,以及代码注释。


课后练习答案

一、概念自测答案

  1. C

    • 解析:函数声明整体提升,包含函数名和函数体,因此可以在声明之前调用。函数表达式(包括箭头函数)遵循变量提升规则,不会整体提升。
  2. D

    • 解析:箭头函数的 this静态绑定的,取决于定义时外层作用域的 this,而非调用方式。D 描述的是普通函数的行为。
  3. **...**(三个点)

    • 解析:...paramName 将剩余参数收集到一个数组中。
  4. 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 注释说明参数和返回值。
  • 代码简洁,遵循命名规范。输出完整代码。”
CATALOG
  1. 1. 第48课:函数基础——声明、表达式、箭头函数、参数默认值与剩余参数
    1. 1.1. 1. 函数声明(Function Declaration)
      1. 1.1.1. 1.1 函数声明会被提升
      2. 1.1.2. 1.2 函数声明的作用域
    2. 1.2. 2. 函数表达式(Function Expression)
      1. 1.2.1. 2.1 函数表达式不会被提升
      2. 1.2.2. 2.2 匿名函数与具名函数表达式
      3. 1.2.3. 2.3 何时使用函数表达式
    3. 1.3. 3. 箭头函数(Arrow Function)
      1. 1.3.1. 3.1 基本语法
      2. 1.3.2. 3.2 隐式返回与对象字面量的冲突
      3. 1.3.3. 3.3 箭头函数与普通函数的关键区别
      4. 1.3.4. 3.4 何时不该用箭头函数
    4. 1.4. 4. 函数参数:默认值与剩余参数
      1. 1.4.1. 4.1 默认参数值
      2. 1.4.2. 4.2 剩余参数 ...
    5. 1.5. 5. 综合示例:三种函数类型的实际应用
    6. 1.6. 课后练习
      1. 1.6.1. 一、概念自测(选择题 / 填空题)
      2. 1.6.2. 二、AI 编程任务:编写面向 AI 的提示词
    7. 1.7. 课后练习答案
      1. 1.7.1. 一、概念自测答案
      2. 1.7.2. 二、AI 编程任务参考答案(提示词示例)