Javascript 零基础教程

JavaScript 匿名函数与立即调用函数表达式 (IIFE)

匿名函数(Anonymous Functions)和立即调用函数表达式IIFE)是 JavaScript 中强大的工具,它们能增强代码的组织性,防止命名冲突,并控制变量作用域。

理解这些概念对于编写整洁、可维护且高效的 JavaScript 代码至关重要,特别是当你开始处理更大、更复杂的项目时。

本章节将深入探讨匿名函数和 IIFE 的细节,为你有效使用它们打下坚实的基础。

1. 匿名函数 (Anonymous Functions)

匿名函数就是没有名字的函数。与需要名字的函数声明不同,匿名函数使用 function 关键字定义,但后面不跟标识符(名字)。

匿名函数通常作为参数传递给其他函数(回调函数),或者被赋值给变量。

1.1 基本语法

匿名函数的基本语法如下:

function() {
  // 函数体
}

注意,function 关键字后面没有名字。

1.2 将匿名函数赋值给变量

匿名函数的一个常见用途是将它们赋值给变量。这就创建了一个函数表达式。

const myFunction = function() {
  console.log("这是一个赋值给变量的匿名函数。");
};

myFunction(); // 调用函数

在这个例子中,匿名函数被赋值给了变量 myFunction。之后,你可以使用该变量名加上括号来调用这个函数。

1.3 匿名函数作为回调 (Callbacks)

匿名函数的另一个常见使用场景是作为回调函数。回调函数是指被作为参数传递给另一个函数,并在第一个函数完成其操作后被执行的函数。

function greet(name, callback) {
  console.log("你好," + name + "!");
  callback();
}

greet("Alice", function() {
  console.log("回调函数已执行。");
});

在这个例子中,匿名函数 function() { console.log("回调函数已执行。"); } 被作为 callback 参数传递给了 greet 函数。greet 函数在打印问候语之后执行了这个回调函数。

1.4 匿名函数的优势

  1. 简洁性: 允许你内联定义函数,而无需单独编写命名的函数声明。
  2. 作用域控制: 当用作回调时,它们有助于保持作用域的整洁,避免创建不必要的全局变量。
  3. 灵活性: 它们可以轻松地作为参数传递给其他函数或赋值给变量,为你的代码提供了更大的灵活性。

1.5 示例:配合数组方法使用匿名函数

匿名函数经常与 mapfilterforEach 等数组方法一起使用。

const numbers = [1, 2, 3, 4, 5];

// 使用 map 和匿名函数来计算每个数字的平方
const squaredNumbers = numbers.map(function(number) {
  return number * number;
});

console.log(squaredNumbers); // 输出: [1, 4, 9, 16, 25]

// 使用 filter 和匿名函数来获取偶数
const evenNumbers = numbers.filter(function(number) {
  return number % 2 === 0;
});

console.log(evenNumbers); // 输出: [2, 4]

在这些例子中,匿名函数被用来定义转换和筛选数组元素的逻辑。

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

立即调用函数表达式(IIFE,发音为 "iffy")是一个在定义后立即执行的 JavaScript 函数。IIFE 用于创建一个新的作用域,这有助于避免变量提升(Hoisting)并防止污染全局作用域。

2.1 基本语法

IIFE 的基本语法涉及将匿名函数包裹在括号中,然后立即调用它。

(function() {
  // 函数体
})();

包裹在 function() { ... } 外面的第一组括号 () 将函数变成了一个表达式。末尾的第二组括号 () 则立即调用该函数。

2.2 为什么使用 IIFE?

  1. 封装 (Encapsulation): IIFE 创建了一个新的作用域,这意味着在 IIFE 内部声明的变量无法从外部访问。这有助于防止命名冲突并保持全局作用域的整洁。
  2. 避免全局作用域污染: 通过将代码封装在 IIFE 中,你可以避免意外创建全局变量,从而防止导致意外行为以及与其他脚本发生冲突。
  3. 模块模式(历史背景): 在引入 ES 模块(ES Modules)之前,IIFE 通常用于在 JavaScript 中实现模块模式,提供了一种创建私有变量和方法的方式。

2.3 示例:基础 IIFE

(function() {
  var message = "来自 IIFE 的问候!";
  console.log(message);
})();

// console.log(message); // 这会导致错误,因为 'message' 在全局作用域中未定义

在这个例子中,变量 message 定义在 IIFE 内部,无法从外部访问。

2.4 向 IIFE 传递参数

你可以像给其他任何函数一样向 IIFE 传递参数。

(function(name) {
  console.log("你好," + name + "!");
})("Bob"); // 传递 "Bob" 作为参数

在这个例子中,字符串 "Bob" 被作为参数传递给 IIFE,函数在控制台打印 "你好,Bob!"。

2.5 从 IIFE 返回值

IIFE 也可以返回值,这些值可以被赋值给变量。

const result = (function() {
  const x = 10;
  const y = 20;
  return x + y;
})();

console.log(result); // 输出: 30

在这个例子中,IIFE 计算 xy 的和并返回结果,该结果随后被赋值给变量 result

2.6 示例:使用 IIFE 保护变量

var counter = 0;
function incrementCounter() {
  counter++;
}
incrementCounter();
incrementCounter();
console.log(counter); // 输出: 2

// 使用 IIFE 创建一个私有计数器
const increment = (function() {
  let privateCounter = 0;
  
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };
})();

increment.increment();
increment.increment();
console.log(increment.value()); // 输出: 2
// console.log(privateCounter); // 会导致错误,因为 privateCounter 在外部作用域中未定义

这个例子演示了如何使用 IIFE 创建一个私有计数器,该计数器无法从 IIFE 外部直接访问或修改,只能通过返回的特定方法操作。

2.7 IIFE 的现代替代方案

随着现代 JavaScript 中 ES 模块 (ES Modules) 的引入,对 IIFE 的需求已经减少。ES 模块提供了一种内置的方式来封装代码并防止全局作用域污染。然而,IIFE 在某些情况下仍然很有用,特别是在旧的代码库中,或者当你需要创建一个快速且简单的临时作用域时。