立即执行函数表达式

IIFE (立即调用函数表达式) 是一个 JavaScript 函数,它在定义后立即运行。IIFE 这个名称由 Ben Alman 在 他的博客 中推广。

js
(function () {
  // …
})();

(() => {
  // …
})();

(async () => {
  // …
})();

它是一种设计模式,也称为 自执行匿名函数,包含两个主要部分。

  1. 第一部分是包含在 分组运算符 () 中的具有词法作用域的匿名函数。这可以防止访问 IIFE 习语中的变量,以及污染全局作用域。
  2. 第二部分通过 () 创建立即调用的函数表达式,JavaScript 引擎将直接解释该函数。

用例

避免污染全局命名空间

因为我们的应用程序可能包含来自不同源文件的许多函数和全局变量,所以限制全局变量的数量很重要。如果我们有一些不需要再次使用的初始化代码,我们可以使用 IIFE 模式。由于我们不会再次使用该代码,因此在这种情况下使用 IIFE 比使用函数声明或函数表达式更好。

js
(() => {
  // some initiation code
  let firstVariable;
  let secondVariable;
})();

// firstVariable and secondVariable will be discarded after the function is executed.

执行异步函数

一个 async IIFE 允许您使用 awaitfor-await,即使是在不支持 顶层 await 的较旧浏览器和 JavaScript 运行时中。

js
const getFileStream = async (url) => {
  // implementation
};

(async () => {
  const stream = await getFileStream("https://domain.name/path/file.ext");
  for await (const chunk of stream) {
    console.log({ chunk });
  }
})();

模块模式

我们还可以使用 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 的 for 循环

在 ES6 引入 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。

另请参阅