JavaScript函数基础语法:声明、表达式与调用方式

作者:异步漫步

时间:2026-10-30

浏览:600

系统解析JavaScript函数的基础语法规范,深入剖析函数声明、函数表达式与箭头函数的定义差异、提升机制及各种场景下的调用方式与this上下文绑定规则。

文章目录

函数是 JavaScript 中组织、复用与抽象逻辑的核心基石。作为“头等公民”(First-class Citizens),JavaScript 函数不仅可以被调用执行,还能像普通数值一样作为参数传递、赋值给变量或作为返回值。掌握函数的创建语法与调用行为,是编写可预测、无副作用代码的前提。本文将完整讲解函数声明(Function Declaration)、函数表达式(Function Expression,包含箭头函数)的语法规范与核心差异,并逐一剖析各种调用方式下的上下文与执行机制。

一、函数声明(Function Declaration)

函数声明是定义函数最直观、最经典的方式。它使用 function 关键字,后跟强制要求的函数标识符(名称)、圆括号中的参数列表,以及花括号包裹的执行函数体。

function calculateTax(amount, rate) {
  const safeRate = rate || 0.05;
  return amount * safeRate;
}

const tax = calculateTax(1000, 0.08);
console.log(tax); // 示例输出:80

函数声明最显著的技术特征是函数声明提升(Function Hoisting)。在 JavaScript 引擎解析执行上下文(如全局环境或函数局部作用域)的编译阶段,声明式函数会被完整提升到当前作用域的顶部。这意味着在物理代码顺序上,调用语句可以位于函数声明语句之前而正常执行。

// 可以在定义之前调用
const result = formatMessage("系统就绪");
console.log(result); // 示例输出:[INFO] 系统就绪

function formatMessage(text) {
  return "[INFO] " + text;
}
注意事项:块级作用域中的函数声明

在 ES6 及更高规范中,严格模式("use strict")下,位于 if、for 等块级结构内部的函数声明,其作用域受限于该代码块内,外部无法直接访问。为避免不同运行时环境的历史兼容性差异,在条件分支内部建议统一使用函数表达式配合 const 进行赋值。

JavaScript函数基础语法:声明、表达式与调用方式
图1:函数声明在编译与运行两个阶段的生命周期及全量提升机制

二、函数表达式(Function Expression)

函数表达式是将一个函数作为值赋予变量或属性的语法结构。与函数声明不同,函数表达式本质是一个赋值表达式,其求值发生在代码实际执行到该行的时刻。

1. 匿名函数表达式与具名函数表达式

函数表达式可以不提供函数名(匿名函数表达式),也可以显式指定内部函数名(具名函数表达式,Named Function Expression,简称 NFE)。

// 匿名函数表达式
const generateId = function(prefix) {
  return prefix + "_" + Math.random().toString(36).substring(2, 9);
};

// 具名函数表达式(NFE)
const calculateFactorial = function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // factorial 仅在函数内部作用域可见,常用于递归
};

console.log(calculateFactorial(4)); // 示例输出:24
// 外部不可直接访问内部标识符 factorial,否则抛出 ReferenceError

2. ES6 箭头函数表达式(Arrow Function)

ES6 引入了更紧凑的箭头函数语法。箭头函数不仅简化了书写,还在语法层面上移除了独立的 this、arguments、super 和 new.target 绑定,其内部直接继承外层执行环境的词法作用域。

// 标准写法
const multiply = (x, y) => {
  return x * y;
};

// 简写形式:单个参数可省略圆括号,单行表达式隐式返回结果
const square = x => x * x;

// 若直接隐式返回对象字面量,需用圆括号包裹对象体
const createPoint = (x, y) => ({ x: x, y: y });

console.log(square(5)); // 示例输出:25
console.log(createPoint(10, 20)); // 示例输出:{ x: 10, y: 20 }

函数表达式与函数声明最关键的运行差异在于提升行为。当使用 const 或 let 定义函数表达式时,变量处于暂存死区(TDZ),在赋值前调用会抛出 ReferenceError;若使用 var,变量提升为 undefined,提前调用会抛出 TypeError: ... is not a function。

JavaScript函数基础语法:声明、表达式与调用方式
图2:函数声明与函数表达式在语法、生命周期提升及工程应用上的全维度对比

三、JavaScript函数的四大核心调用方式

在 JavaScript 中,函数内部的执行上下文(特别是 this 对象的指向)不是在定义时静态决定的(箭头函数除外),而是取决于函数被调用的具体语法形式。理解以下四种调用方式是掌握函数行为的关键。

1. 函数直接调用(独立调用)

直接通过标识符加圆括号调用函数,例如 fn()。此时函数不依附于任何对象上下文。

  • 非严格模式:函数内部的 this 默认指向全局对象(浏览器环境为 window,Node.js 为 global)。
  • 严格模式("use strict"):防止全局污染,this 保持为 undefined。
function showContext() {
  "use strict";
  return this;
}

console.log(showContext()); // 示例输出:undefined

2. 方法调用(作为对象属性调用)

当函数被赋值为对象的某个属性,并通过属性访问运算符(obj.fn() 或 obj["fn"]())发起调用时,属于方法调用。此时 this 会自动隐式绑定到该方法所属的对象实体。

const counter = {
  count: 10,
  increment: function(step) {
    this.count += step;
    return this.count;
  }
};

console.log(counter.increment(5)); // 示例输出:15(this 指向 counter 对象)

// 容易出错的上下文丢失场景:将方法抽离为独立变量
const detachedFn = counter.increment;
// detachedFn(5); // 严格模式下报错:TypeError: Cannot read properties of undefined

3. 构造函数调用(new 运算符调用)

在函数调用前加上 new 关键字,例如 new User("Alice"),该调用转变为构造过程。JavaScript 引擎会按序执行以下操作:

  1. 在内存中自动创建一个全新的空对象。
  2. 将该新对象的内部原型 [[Prototype]] 连接到构造函数的 prototype 属性。
  3. 将函数内部的 this 绑定到这个新建对象。
  4. 执行构造函数内部的代码(通常是为 this 添加属性)。
  5. 如果构造函数没有显式返回非空对象,则默认返回这个新建的 this 实例。
function UserAccount(username, role) {
  this.username = username;
  this.role = role || "viewer";
}

const user1 = new UserAccount("admin_user", "superadmin");
console.log(user1.username); // 示例输出:admin_user

4. 间接调用与显式绑定(call、apply 与 bind)

所有标准 JavaScript 函数均继承自 Function.prototype,因而具备 call、apply 与 bind 方法。这些方法允许开发者显式指定调用时的 this 对象。

  • fn.call(context, arg1, arg2, ...):立即执行函数,第一个参数指定 this,后续参数逐个依次展开传入。
  • fn.apply(context, [arg1, arg2, ...]):立即执行函数,第一个参数指定 this,参数以数组或类数组形式整体传入。
  • fn.bind(context, arg1, ...):不会立即执行,而是返回一个永久硬绑定了目标 this 和预设参数的新函数。
function introduce(greeting, punctuation) {
  return greeting + ",我是 " + this.name + punctuation;
}

const person = { name: "李工" };

// 1. call: 逗号依次传参
console.log(introduce.call(person, "你好", "!")); // 示例输出:你好,我是 李工!

// 2. apply: 数组传参
console.log(introduce.apply(person, ["早上好", "。"])); // 示例输出:早上好,我是 李工。

// 3. bind: 返回预绑定函数
const boundIntroduce = introduce.bind(person, "欢迎");
console.log(boundIntroduce("~")); // 示例输出:欢迎,我是 李工~
JavaScript函数基础语法:声明、表达式与调用方式
图3:函数调用方式与 this 上下文绑定决策树

四、函数参数处理机制

JavaScript 在参数处理上具备极高的动态灵活性。定义时的参数称为形参(Parameters),实际传入的值称为实参(Arguments)。JavaScript 并不要求形参和实参在数量上严格一致。

1. ES6 默认参数(Default Parameters)

在 ES6 之前,处理缺省参数通常需要在函数体内部进行逻辑判断(如 timeout = timeout || 3000;),这会在传入合法假值(如 0 或 false)时产生非预期行为。ES6 提供了直接在形参列表中赋予默认值的语法:

function connectDatabase(host = "127.0.0.1", port = 5432, timeout = 3000) {
  return {
    url: host + ":" + port,
    timeout: timeout
  };
}

// 仅当传入 undefined 或未传参时触发默认值,传入 null 不会触发
console.log(connectDatabase()); 
// 示例输出:{ url: "127.0.0.1:5432", timeout: 3000 }

console.log(connectDatabase("192.168.1.10", undefined, 5000));
// 示例输出:{ url: "192.168.1.10:5432", timeout: 5000 }

2. 剩余参数(Rest Parameters)与传统 arguments

在处理不定数量的入参时,推荐优先使用 ES6 的剩余参数(...args),它会把剩余实参聚合成一个真正的 JavaScript 数组:

// 剩余参数:args 是真正的 Array 实例,可以直接调用 map、reduce 等数组原生方法
function sumAll(multiplier, ...numbers) {
  const sum = numbers.reduce((total, val) => total + val, 0);
  return sum * multiplier;
}

console.log(sumAll(2, 10, 20, 30)); // 示例输出:120

相比之下,传统非箭头函数中内置的 arguments 对象是一个“类数组对象”(Array-like Object),仅具备 length 属性与索引读取能力,缺少数组原型方法;且箭头函数内部不存在 arguments 绑定。

五、工程总结与选型参考

综合函数定义语法与调用特性的技术差异,在日常前端与 Node.js 工程中可参考以下实践准则:

  • 模块顶层导出:优先使用标准函数声明。具备明确函数名与良好的语义性,便于文档生成与快速理解模块 API。
  • 组件与内部辅助逻辑:使用 const 声明的函数表达式,强制执行单向流逻辑,规避意外提升覆盖。
  • 回调与高阶函数:优先使用箭头函数。语法紧凑清晰,且自动保持外部词法环境的 this,避免传统开发中常见的 var self = this; 或频繁 .bind(this)。
  • 对象方法定义:采用 ES6 对象方法简写语法 { method() { ... } },既能正确绑定对象上下文,又具备简明可读性。
JavaScript函数基础语法:声明、表达式与调用方式
图4:JavaScript函数基础语法形态、调用机制与工程选型全景矩阵
特性维度 函数声明 普通函数表达式 箭头函数表达式
提升特性(Hoisting) 完整提升(函数名与函数体均提前可用) 仅变量提升(const/let 处于 TDZ 报错,var 值为 undefined) 仅变量提升(行为同变量表达式)
内部 this 指向 运行时动态决定(由调用语法形式确定) 运行时动态决定(由调用语法形式确定) 固定继承外部词法作用域(不可被 call/apply 改变)
能否作为构造函数(new) 可以(具备 prototype 属性) 可以(具备 prototype 属性) 不可(无 prototype 属性,调用会抛出 TypeError)
arguments 对象支持 原生支持类数组 arguments 原生支持类数组 arguments 不提供 arguments,需使用剩余参数 ...args

通过理解函数声明的提升原理、函数表达式的灵活赋值机制、箭头函数的词法作用域规则,以及四种经典调用方式对执行上下文的影响,开发者便能够在实际编码中准确判断 this 归属,杜绝作用域泄漏与未定义报错,编写出清晰、严谨的 JavaScript 程序。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章