function 表达式

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

function 关键字可用于在表达式中定义函数。

你也可以使用function 声明箭头语法来定义函数。

试一试

const getRectArea = function (width, height) {
  return width * height;
};

console.log(getRectArea(3, 4));
// Expected output: 12

语法

js
function (param0) {
  statements
}
function (param0, param1) {
  statements
}
function (param0, param1, /* …, */ paramN) {
  statements
}

function name(param0) {
  statements
}
function name(param0, param1) {
  statements
}
function name(param0, param1, /* …, */ paramN) {
  statements
}

注意:表达式语句不能以关键字 function 开头,以避免与function 声明产生歧义。只有当 function 关键字出现在不能接受语句的上下文中时,它才开始一个表达式。

参数

name 可选

函数名称。可以省略,这种情况下函数是匿名的。该名称仅在函数体内部局部有效。

paramN 可选

函数形式参数的名称。有关参数的语法,请参阅函数参考

statements 可选

构成函数体的语句。

描述

function 表达式与function 声明非常相似,并且具有几乎相同的语法。function 表达式和 function 声明之间的主要区别是函数名称,在 function 表达式中可以省略该名称以创建匿名函数。function 表达式可以用作IIFE(立即调用函数表达式),它在定义后立即运行。有关更多信息,请参阅关于函数的章节。

函数表达式提升

JavaScript 中的函数表达式不像函数声明那样被提升。你不能在创建函数表达式之前使用它们。

js
console.log(notHoisted); // undefined
// Even though the variable name is hoisted,
// the definition isn't. so it's undefined.
notHoisted(); // TypeError: notHoisted is not a function

var notHoisted = function () {
  console.log("bar");
};

命名函数表达式

如果你想在函数体内部引用当前函数,你需要创建一个命名函数表达式。这个名称只在函数体(作用域)内部局部有效。这避免了使用已弃用的arguments.callee属性来递归调用函数。

js
const math = {
  factorial: function factorial(n) {
    console.log(n);
    if (n <= 1) {
      return 1;
    }
    return n * factorial(n - 1);
  },
};

math.factorial(3); // 3;2;1;

如果一个函数表达式被命名,函数的name属性将设置为该名称,而不是从语法推断的隐式名称(例如函数被赋值的变量)。

与声明不同,函数表达式的名称是只读的。

js
"use strict";

function foo() {
  foo = 1;
}
foo();
console.log(foo); // 1
(function foo() {
  foo = 1; // TypeError: Assignment to constant variable.
})();

示例

使用函数表达式

以下示例定义了一个匿名函数并将其赋值给 x。该函数返回其参数的平方。

js
const x = function (y) {
  return y * y;
};

将函数用作回调

更常见的是它被用作回调

js
button.addEventListener("click", function (event) {
  console.log("button is clicked!");
});

使用立即调用函数表达式 (IIFE)

IIFE 是一种常见的模式,用于在需要单个表达式的位置,在其自己的作用域中执行任意数量的语句(并可能返回一个值)。许多传统的 IIFE 用例已被诸如模块块作用域声明等新语法特性所取代。IIFE 本身现在更常使用箭头函数编写,但其思想保持不变。通常,IIFE 看起来像这样。

js
// standard IIFE
(function () {
  // statements…
})();

// IIFE with arguments
(function (a, b) {
  console.log(a + b);
})(1, 2); // logs 3

// IIFE being used to initialize a variable
const value = (() => {
  const randomValue = Math.random();
  if (randomValue > 0.5) {
    return "heads";
  }
  return "tails";
})();

在此,我们将介绍几个带有示例的用例。

避免在脚本代码中污染全局命名空间

所有脚本的顶级作用域都是共享的,这可能包含来自不同文件的许多函数和全局变量,因此为了避免名称冲突,限制全局声明的名称数量很重要(这在模块中得到了很大缓解,但有时限制临时变量的作用域仍然很有用,特别是当文件非常长时)。如果我们有一些不需要再次使用的初始化代码,我们可以使用 IIFE 模式,这比使用函数声明或函数表达式更好,因为它确保代码只在此处运行一次。

js
// top-level of a script (not a module)

var globalVariable = (() => {
  // some initialization code
  let firstVariable = something();
  let secondVariable = somethingElse();
  return firstVariable + secondVariable;
})();

// firstVariable and secondVariable cannot be accessed outside of the function body.

模块模式

我们还会使用 IIFE 来创建私有和公共变量以及方法。有关模块模式的更复杂用法和 IIFE 的其他用法,你可以查阅 Addy Osmani 的《学习 JavaScript 设计模式》一书。

js
const makeWithdraw = (balance) =>
  ((copyBalance) => {
    let balance = copyBalance; // This variable is private
    const doBadThings = () => {
      console.log("I will do bad things with your money");
    };
    doBadThings();
    return {
      withdraw(amount) {
        if (balance >= amount) {
          balance -= amount;
          return balance;
        }
        return "Insufficient money";
      },
    };
  })(balance);

const firstAccount = makeWithdraw(100); // "I will do bad things with your money"
console.log(firstAccount.balance); // undefined
console.log(firstAccount.withdraw(20)); // 80
console.log(firstAccount.withdraw(30)); // 50
console.log(firstAccount.doBadThings); // undefined; this method is private
const secondAccount = makeWithdraw(20); // "I will do bad things with your money"
console.log(secondAccount.withdraw(30)); // "Insufficient money"
console.log(secondAccount.withdraw(20)); // 0

ES6 之前的 var 循环

在块作用域的 letconst 声明引入之前,我们可以在一些旧代码中看到 IIFE 的以下用法。使用 var 语句,我们只有函数作用域和全局作用域。假设我们想创建两个按钮,文本分别为 Button 0 和 Button 1,当我们点击它们时,我们希望它们分别弹出 0 和 1。以下代码不起作用:

js
for (var i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  button.onclick = function () {
    console.log(i);
  };
  document.body.appendChild(button);
}
console.log(i); // 2

当点击时,Button 0 和 Button 1 都弹出 2,因为 i 是全局的,其最终值为 2。为了在 ES6 之前解决这个问题,我们可以使用 IIFE 模式。

js
for (var i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  button.onclick = (function (copyOfI) {
    return function () {
      console.log(copyOfI);
    };
  })(i);
  document.body.appendChild(button);
}
console.log(i); // 2

点击时,Button 0 和 Button 1 分别弹出 0 和 1。变量 i 是全局定义的。使用 let 语句,我们可以简单地这样做:

js
for (let i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  button.onclick = function () {
    console.log(i);
  };
  document.body.appendChild(button);
}
console.log(i); // Uncaught ReferenceError: i is not defined.

点击时,这些按钮分别弹出 0 和 1。

表达式位置中的控制流语句

IIFE 使我们能够在表达式中使用诸如 switch 等语言构造。

js
someObject.property = (() => {
  switch (someVariable) {
    case 0:
      return "zero";
    case 1:
      return "one";
    default:
      return "unknown";
  }
})();

这种方法在您希望将变量声明为 const 但在初始化期间被迫使用 letvar 的情况下特别有用。

js
let onlyAssignedOnce;
try {
  onlyAssignedOnce = someFunctionThatMightThrow();
} catch (e) {
  onlyAssignedOnce = null;
}

使用 IIFE,我们可以将变量声明为 const

js
const onlyAssignedOnce = (() => {
  try {
    return someFunctionThatMightThrow();
  } catch (e) {
    return null;
  }
})();

规范

规范
ECMAScript® 2026 语言规范
# sec-function-definitions

浏览器兼容性

另见