深入讲解JavaScript函数的3种核心书写方式:函数声明、函数表达式与箭头函数。详解代码语法、变量提升机理、动态this与词法this差异及项目实战选型指南。
JavaScript函数基础:声明、表达式与箭头函数怎么写
时间:2026-11-02
浏览:202
文章目录
在 JavaScript 中,函数是一等公民(First-Class Citizens),既是封装可复用业务逻辑的基石,也能作为参数传递和值返回。编写函数主要有三种经典写法:函数声明(Function Declaration)、函数表达式(Function Expression)以及 ES6 引入的箭头函数(Arrow Function)。它们不仅在语法字面上存在差异,更在运行时的变量提升机理、this 上下文绑定、arguments 访问权限及是否可作为构造函数上有着根本区别。本文将系统拆解这三种语法的标准写法、执行原理与选型准则。
一、函数声明:经典具名与完全提升
函数声明是 JavaScript 中最传统也是最直观的函数定义方式。其基础语法由 function 关键字、强制要求存在的函数标识符(名称)、形参列表以及由大括号包裹的函数体构成。
// 基础语法结构
function add(a, b) {
return a + b;
}
// 执行调用
const result = add(3, 5);
console.log(result); // 示例输出: 8
核心机理:完全声明提升(Hoisting)
JavaScript 引擎在正式逐行执行代码之前,会预先经历编译与语法分析阶段(Creation Phase)。在这一阶段中,所有通过函数声明定义的函数,其函数名称和函数体实现会被完整提升并注册到当前作用域顶部。因此,在物理代码行中,即使调用语句写在声明语句之前,程序也能正常执行而不会报错。
// 运行示例:先调用,后声明
greet("开发者"); // 正常输出: "你好,开发者!"
function greet(name) {
return `你好,${name}!`;
}
函数声明还具备独立的 this 绑定、内置的 arguments 类数组对象,以及完整的 prototype 原型属性,因此它既可以直接作为普通逻辑函数调用,也可以作为面向对象风格的构造函数(配合 new 关键字实例化)。
二、函数表达式:作为变量值的灵活绑定
函数表达式是将一个函数赋值给变量的语法结构。右侧的函数可以是匿名的(最常见),也可以是具名的。在现代开发规范中,通常强烈推荐使用 const 来承接函数表达式,防止函数引用在后续逻辑中被意外覆盖修改。
// 1. 匿名函数表达式(推荐使用 const)
const multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 6)); // 示例输出: 24
// 2. 具名函数表达式(Named Function Expression)
// 内部函数名 factorial 仅在自身函数体内可访问,常用于递归和精准调用栈排查
const factorial = function calc(n) {
if (n <= 1) return 1;
return n * calc(n - 1);
};
console.log(factorial(4)); // 示例输出: 24
// 注意:如果在外部直接调用 calc(4),会抛出 ReferenceError
核心机理:变量提升与暂时性死区(TDZ)
函数表达式与函数声明最大的本质区别在于生命周期与求值时机。当使用 const 或 let 声明变量并赋值函数时,变量名虽在作用域中注册,但在代码实际执行到该赋值行之前,该变量处于暂时性死区(Temporal Dead Zone, TDZ)中。如果在赋值语句之前尝试调用,引擎会立即抛出 ReferenceError。
// 错误示范:在初始化前提前调用函数表达式
calculateTotal(100);
// 运行报错: ReferenceError: Cannot access 'calculateTotal' before initialization
const calculateTotal = function(price) {
return price * 1.1;
};
var calculate = function(...) {},由于 var 会将变量提升并初始化为 undefined,提前调用时抛出的将是 TypeError: calculate is not a function,更容易引发难以察觉的静默缺陷。因此,应统一使用 const。
三、箭头函数:现代简洁语法与词法 this
ES6(ECMAScript 2015)引入了箭头函数(Arrow Function),以 => 标记为核心。箭头函数大幅度精简了函数书写,同时彻底重构了内部的上下文规则,是现代前端高频使用的形态。
1. 各种语法简写形式
箭头函数根据参数个数和返回值逻辑,提供了多级简写规则:
- 标准多参数与多行块:使用圆括号包裹参数,使用花括号包裹函数体并显式
return。 - 单参数省略括号:当只有一个形参时,可以省略包裹参数的圆括号。
- 单行表达式隐式返回:如果函数体只包含一行求值表达式,可省略花括号与
return关键字,该表达式的计算结果自动作为返回值。 - 返回对象字面量陷阱:若隐式返回一个对象,必须使用圆括号包裹
({ id: 1 }),否则花括号会被引擎误解析为函数体语句块。
// 标准多参数写法
const sum = (a, b) => {
return a + b;
};
// 单参数省略圆括号 + 单行隐式返回
const square = x => x * x;
console.log(square(5)); // 示例输出: 25
// 关键易错点:隐式返回对象字面量(必须用圆括号包裹)
const createUser = (id, name) => ({
id: id,
name: name,
createdAt: Date.now()
});
console.log(createUser(101, "Alice")); // 示例输出: { id: 101, name: 'Alice', createdAt: ... }
2. 核心特性:词法作用域 this 与能力约束
箭头函数并非只是传统函数的“语法糖”,它在语言规范层面删除了多项传统能力,并重新定义了上下文:
- 无独立 this(静态捕获):箭头函数内部没有属于自己的
this绑定。其内部的this严格继承自外层包裹它的词法作用域(Lexical Scope),无论后续如何调用,甚至使用call()、apply()、bind(),都无法改变其this指向。 - 无 arguments 对象:箭头函数内部不存在
arguments。如需收集不确定数量的入参,必须使用 ES6 剩余参数语法(...args) => {}。 - 不可作为构造函数(无 prototype):箭头函数没有
prototype属性,严禁配合new关键字调用,否则会抛出TypeError: ... is not a constructor。 - 不能用作生成器(Generator):函数体内部不能使用
yield关键字。
// 实例对比:在定时器/异步回调中保留 this
const timerManager = {
name: "任务调度器",
// 传统函数作为方法
startTraditional() {
setTimeout(function() {
// 传统函数独立执行,this 丢失指向全局/undefined
console.log("传统回调:", this.name);
}, 100);
},
// 箭头函数作为回调
startArrow() {
setTimeout(() => {
// 箭头函数直接继承外层 startArrow 执行时的 this (timerManager)
console.log("箭头回调:", this.name);
}, 100);
}
};
timerManager.startTraditional(); // 输出: 传统回调: undefined (非严格模式下为 window.name)
timerManager.startArrow(); // 正常输出: 箭头回调: 任务调度器
四、三者核心维度横向对比
为了在编码与代码评审中迅速甄别,下表将函数声明、函数表达式与箭头函数在关键语言维度上的特性进行了集中对照:
| 特性维度 | 函数声明 (Declaration) | 函数表达式 (Expression) | 箭头函数 (Arrow Function) |
|---|---|---|---|
| 语法形式 | function fn() {} |
const fn = function() {}; |
const fn = () => {}; |
| 提升行为 (Hoisting) | 完全提升(可在声明前安全调用) | 变量提升但受 TDZ 限制(不可提前调用) | 变量提升但受 TDZ 限制(不可提前调用) |
| this 确定方式 | 运行时动态绑定(由调用方决定) | 运行时动态绑定(由调用方决定) | 定义时词法绑定(捕获外层上下文) |
| 参数对象 | 拥有 arguments 对象 |
拥有 arguments 对象 |
无 arguments(使用 ...args) |
| 构造能力 | 支持 new 实例化 |
支持 new 实例化 |
不支持(抛出 TypeError) |
| 原型属性 (prototype) | 有(默认指向自身原型实例) | 有(默认指向自身原型实例) | 无(undefined) |
五、工程实战选型指南与决策流程
在实际项目编码中,选择哪一种函数写法并不是单纯的个人偏好,而是取决于上下文职责:
- 优先选用箭头函数的场景:
- 数组高阶变换与迭代:
arr.map(x => x * 2)、arr.filter(...)。 - 异步流程与事件回调:
Promise.then(() => ...)、setTimeout、requestAnimationFrame等需要继承外层this的环境。 - 无副作用的纯计算函数:直接返回计算结果的工具短函数。
- 数组高阶变换与迭代:
- 优先选用函数声明的场景:
- 模块顶层导出与核心 API:在文件最外层定义通用的模块级业务函数。函数声明的提升特性允许将主流程代码置于文件上方,将具体的辅助函数置于下方,符合从概览到细节的阅读体验。
- 面向对象编程中的传统构造函数:如果未采用 ES6
class语法,需要构建自定义原型链时。
- 优先选用函数表达式的场景:
- 按需条件装配与策略模式:需要根据环境分支或运行时状态动态决定函数引用的场景(例如在不同操作系统或特性检测后为变量赋予不同的具体实现)。
- 保护变量不被意外提升:需要严格确保函数只能在代码逻辑流到达之后才能被执行。
六、高频踩坑场景与规避方案
陷阱 1:在对象字面量方法中误用箭头函数
初学者常误以为箭头函数在任何场合都比传统函数更现代化,于是在声明对象的方法时使用了箭头函数:
// ❌ 错误写法
const counter = {
count: 0,
increment: () => {
this.count++; // 这里的 this 并没有指向 counter 对象,而是捕获了外层(如 window 或 module.exports)
}
};
counter.increment();
console.log(counter.count); // 仍然是 0
// ✓ 正确写法:使用 ES6 对象方法简写(等同于传统函数表达式)
const safeCounter = {
count: 0,
increment() {
this.count++;
}
};
safeCounter.increment();
console.log(safeCounter.count); // 正常变为 1
陷阱 2:在 DOM 事件监听中期望通过 this 获取触发元素
在原生 DOM 事件处理中,浏览器默认会将触发事件的 DOM 元素绑定到回调函数的 this 上。若使用箭头函数,this 将被锁死在外层作用域,无法获取当前点击元素。
const button = document.querySelector("#submit-btn");
// 若使用箭头函数,需显式通过事件对象 event.currentTarget 访问元素
button.addEventListener("click", (event) => {
console.log(event.currentTarget); // 安全可靠获取当前按钮节点
});
掌握函数声明的提升能力、函数表达式的装配控制力以及箭头函数的词法 this 约束,能够帮助你在构建复杂 JavaScript 应用时,写出逻辑严谨、执行可靠且易于维护的高质量代码。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。