JavaScript函数基础:声明、表达式与箭头函数怎么写

作者:青柠时间:2026-11-02 10:27:47浏览:205

深入讲解JavaScript函数的3种核心书写方式:函数声明、函数表达式与箭头函数。详解代码语法、变量提升机理、动态this与词法this差异及项目实战选型指南。

JavaScript 教程 阅读时间:约 8 分钟 适用环境:现代浏览器 / Node.js 均通用
JavaScript函数基础:声明、表达式与箭头函数怎么写
图 0:JavaScript 核心函数形态与关键特性总览

在 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}!`;
}
JavaScript函数基础:声明、表达式与箭头函数怎么写
图 1:函数声明在 V8/JS 引擎解析期与执行期的完全提升流程

函数声明还具备独立的 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。

JavaScript函数基础:声明、表达式与箭头函数怎么写
图 2:函数表达式基于 const/let 声明时的时序拦截与 TDZ 机制
// 错误示范:在初始化前提前调用函数表达式
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 关键字。
JavaScript函数基础:声明、表达式与箭头函数怎么写
图 3:传统普通函数动态 this 与箭头函数词法作用域 this 绑定机理对比
// 实例对比:在定时器/异步回调中保留 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)

五、工程实战选型指南与决策流程

在实际项目编码中,选择哪一种函数写法并不是单纯的个人偏好,而是取决于上下文职责:

JavaScript函数基础:声明、表达式与箭头函数怎么写
图 4:现代 JavaScript 函数形式选型判断流程
  1. 优先选用箭头函数的场景:
    • 数组高阶变换与迭代:arr.map(x => x * 2)、arr.filter(...)。
    • 异步流程与事件回调:Promise.then(() => ...)、setTimeout、requestAnimationFrame 等需要继承外层 this 的环境。
    • 无副作用的纯计算函数:直接返回计算结果的工具短函数。
  2. 优先选用函数声明的场景:
    • 模块顶层导出与核心 API:在文件最外层定义通用的模块级业务函数。函数声明的提升特性允许将主流程代码置于文件上方,将具体的辅助函数置于下方,符合从概览到细节的阅读体验。
    • 面向对象编程中的传统构造函数:如果未采用 ES6 class 语法,需要构建自定义原型链时。
  3. 优先选用函数表达式的场景:
    • 按需条件装配与策略模式:需要根据环境分支或运行时状态动态决定函数引用的场景(例如在不同操作系统或特性检测后为变量赋予不同的具体实现)。
    • 保护变量不被意外提升:需要严格确保函数只能在代码逻辑流到达之后才能被执行。

六、高频踩坑场景与规避方案

陷阱 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 应用时,写出逻辑严谨、执行可靠且易于维护的高质量代码。

作者最新文章
相关文章
更多