深入讲解 JavaScript 中函数声明、函数表达式与箭头函数的语法定义、调用方式、底层机制(提升与 this 绑定)及适用场景对比,提供完整可运行代码示例与结构图解。
JavaScript函数声明、表达式与箭头函数的定义及调用示例
时间:2026-10-30
浏览:116
文章目录
在 JavaScript 中,函数是组织逻辑、复用代码以及实现异步控制流的基石。定义函数最常见的三种方式分别为:函数声明(Function Declaration)、函数表达式(Function Expression)以及 ES6 引入的箭头函数(Arrow Function)。虽然它们都能接收参数并返回值,但在语法结构、变量提升(Hoisting)、this 指向规则以及构造函数能力上存在根本性的差异。本文将逐一讲解三者的定义语法、调用方式、底层运行机制与常见误区,并通过完整的代码与架构图示助你在开发中做出清晰准确的技术选择。
一、函数声明(Function Declaration)
函数声明是 JavaScript 中最传统也是最直观的函数定义方式。它使用独立的 function 关键字开头,后接函数名称、参数列表和由大括号包裹的函数体。
1. 语法定义与基础调用
函数声明属于独立语句,定义时必须显式指定函数名称,该名称会被直接绑定到当前作用域中。
// 1. 函数声明定义
function calculateDiscount(price, discountRate) {
const finalPrice = price * (1 - discountRate);
return finalPrice;
}
// 2. 函数调用示例
const payable = calculateDiscount(200, 0.15);
console.log("折后应付金额:", payable); // 输出:170
2. 核心特征:完整的函数提升(Hoisting)
在 JavaScript 引擎执行代码之前,会先进行编译分析。在此阶段,函数声明及其函数体会被完整提升至当前作用域的最顶部。这意味着你可以在书写该函数的物理位置之前安全地调用它。
// 在函数声明前直接调用
const greeting = getWelcomeMessage("张三");
console.log(greeting); // 正常输出:"您好,张三!"
// 函数声明位于调用代码下方
function getWelcomeMessage(username) {
return "您好," + username + "!";
}
这种机制使得开发者可以先在文件顶部组织清晰的主干调用流程,而将具体的底层细节函数存放在文件底部,提高模块主干逻辑的可读性。
二、函数表达式(Function Expression)
在 JavaScript 中,函数是“头等公民”(First-class Citizens),这意味着函数本身也是一种值。将一个函数作为值赋给一个变量、对象属性或数组元素,这种语法形式被称为函数表达式。
1. 匿名函数表达式与调用
最常见的是将匿名函数赋值给 const 或 let 声明的变量,通过该变量名执行调用:
// 匿名函数表达式
const multiply = function(a, b) {
return a * b;
};
// 通过变量名调用
const result = multiply(6, 7);
console.log("乘积结果:", result); // 输出:42
const 或 let 声明函数表达式,在代码执行到声明语句之前调用该函数,会抛出 ReferenceError: Cannot access 'multiply' before initialization;若使用旧式的 var,变量提升为 undefined,提前调用会抛出 TypeError: multiply is not a function。
2. 具名函数表达式(Named Function Expression)
函数表达式也可以给函数本身指定一个内部名称。需要注意:这个函数名称仅在该函数内部作用域有效,常用于递归调用或在错误堆栈跟踪中清晰展示函数名,而在外部无法直接通过该名称调用。
// 具名函数表达式:外部使用 factorial,内部递归使用 calcFact
const factorial = function calcFact(n) {
if (n <= 1) {
return 1;
}
return n * calcFact(n - 1); // 内部递归
};
console.log(factorial(5)); // 输出:120
// console.log(calcFact(5)); // 抛出 ReferenceError: calcFact is not defined
三、箭头函数(Arrow Function)
ECMAScript 2015 (ES6) 引入了箭头函数。它提供了一种更加精简的函数书写语法,并且对执行上下文中的 this 进行了重新定义。
1. 语法简写规则
箭头函数的书写方式非常灵活,根据参数个数和函数体结构,主要有以下简写规则:
- 标准多参数与多行体:参数用小括号包裹,函数体使用花括号并显式使用
return。 - 单参数省略括号:当且仅当只有一个参数时,可以省略包裹参数的圆括号。
- 单行表达式隐式返回:当函数体只有一行表达式时,可以省略花括号与
return关键字,该表达式的计算结果会自动返回。 - 返回对象字面量:若隐式返回的内容是 JavaScript 对象字面量,必须用小括号将其包裹
({ key: value }),避免引擎将对象的花括号误判为函数体的代码块。
// 规则 1:标准形式
const add = (a, b) => {
return a + b;
};
// 规则 2 & 3:单参数 + 隐式返回
const square = x => x * x;
// 规则 4:隐式返回对象字面量(需加小括号)
const createUser = (id, name) => ({
id: id,
name: name,
createdAt: Date.now()
});
console.log("计算平方:", square(8)); // 输出:64
console.log("创建用户:", createUser(101, "李四"));
2. 作为回调函数的高频调用场景
箭头函数最适合作为数组高阶方法(如 map, filter, reduce)或异步 Promise 链中的回调函数:
const scores = [65, 88, 45, 92, 74, 58];
// 筛选及格分数并统一增加 5 分
const adjustedScores = scores
.filter(score => score >= 60)
.map(score => score + 5);
console.log(adjustedScores); // 输出:[93, 97, 79]
四、关键机制深入解析:this 指向与底层特性
理解普通函数(声明与表达式)与箭头函数的底层机制差异,是编写无 bug JavaScript 代码的关键。
1. 动态 this vs 词法 this(Lexical this)
普通函数中的 this 是动态绑定的,取决于函数是如何被调用的,而非定义的位置:
- 作为对象方法调用时(如
user.getName()),this指向该对象。 - 作为独立函数调用时(如
fn()),在非严格模式下指向全局对象window/global,严格模式('use strict')下为undefined。 - 通过
call、apply或bind可以显式重置this。
相比之下,箭头函数没有自己的 this。箭头函数内部的 this 会像普通作用域链变量一样,在定义时直接捕获外层作用域的 this。且无论之后通过何种方式调用,甚至使用 call/apply,都无法改变箭头函数中绑定的 this。
const timerHandler = {
counter: 0,
// 普通函数作为对象方法:this 指向 timerHandler
startWithRegularFunction: function() {
setTimeout(function() {
// 这里的普通回调函数在独立调用时执行
// this 丢失!严格模式为 undefined,非严格为 window
console.log("普通函数回调内部 this:", this);
}, 100);
},
// 箭头函数回调:捕获 startWithArrowFunction 运行时的 this
startWithArrowFunction: function() {
setTimeout(() => {
this.counter += 1;
console.log("箭头函数捕获外层 this,counter =", this.counter); // 输出:1
}, 100);
}
};
timerHandler.startWithArrowFunction();
2. arguments 对象与 Rest 剩余参数
在普通函数中,你可以通过内置的 arguments 类数组对象访问调用时传入的所有实参,而无需提前在形参列表中声明。然而箭头函数没有自己的 arguments,如果在箭头函数内部使用 arguments,它访问的其实是外层函数的 arguments。
现代 JavaScript 中推荐使用 Rest 剩余参数(...args) 代替 arguments,这一语法对三种函数均适用且返回真正的数组:
// 传统函数可以使用 arguments
function sumOld() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// 现代箭头函数必须使用剩余参数 (...args)
const sumModern = (...nums) => nums.reduce((acc, cur) => acc + cur, 0);
console.log("传统 arguments 求和:", sumOld(1, 2, 3, 4)); // 10
console.log("Rest 参数箭头函数求和:", sumModern(1, 2, 3, 4)); // 10
3. 构造函数能力(new 关键字)
普通函数声明和表达式都可以作为构造函数,配合 new 实例化对象,它们在定义时由引擎自动挂载 prototype 属性。但是,箭头函数没有 prototype 属性,也没有 internal [[Construct]] 方法,绝对不能用作构造函数,否则会直接抛出运行时异常:
// 普通函数作为构造函数:完全合法
function Person(name) {
this.name = name;
}
const p = new Person("张三");
console.log(p.name); // 输出:"张三"
// 箭头函数尝试作为构造函数:报错!
const Animal = (name) => {
this.name = name;
};
// const a = new Animal("Cat"); // TypeError: Animal is not a constructor
五、综合对比与选型指南
为了在实际开发中快速决策,下表总结了三者在各技术维度的核心区别:
| 特性维度 | 函数声明 (Declaration) | 函数表达式 (Expression) | 箭头函数 (Arrow Function) |
|---|---|---|---|
| 语法形式 | function fn() {} |
const fn = function() {} |
const fn = () => {} |
| 提升表现 | 整体完全提升(函数名与函数体) | 仅提升变量,依赖赋值时机 | 仅提升变量,依赖赋值时机 |
| this 绑定 | 动态绑定(由调用者决定) | 动态绑定(由调用者决定) | 词法绑定(继承定义所在的外层环境) |
| arguments | 有专属 arguments 对象 | 有专属 arguments 对象 | 无,需使用 ...rest 参数 |
| new 构造能力 | 支持,拥有 prototype |
支持,拥有 prototype |
不支持,抛出 TypeError |
| 生成器支持 | 支持 function* |
支持 function* |
不支持 |
工程实践中的避坑法则
- 不要在对象字面量属性中盲目使用箭头函数:如果一个函数需要通过
this访问同对象的其它属性,必须使用标准的对象方法简写(如login() { ... }),切忌写成login: () => { this.status ... },否则this会飘移至最外层作用域。 - DOM 事件监听器的谨慎选用:在原生 DOM 监听中,如果希望通过
this直接获取触发事件的 DOM 元素节点,应使用普通函数;若使用箭头函数,this将不再指向该元素,此时必须通过事件参数e.currentTarget显式获取。 - 模块顶级导出函数:对于工具模块中独立对外暴露的核心 API,使用函数声明可赋予代码清晰的命名语义与堆栈跟踪信息,配合 ES 模块的
export function是业界推崇的清晰范式。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。