闭包

闭包是一种技术,它将函数与其周围的变量(词法环境)捆绑在一起。通常,闭包在像 JavaScript 这样的函数式编程语言中实现,并用于支持柯里化(Currying)。


词法环境

首先,我们展示函数如何访问其词法环境。这本身并不是闭包。

"use strict";

function incrementByCertainValue(param) {
  return param + certainValue;
}

let certainValue = 10;
alert(incrementByCertainValue(8)); // 输出:18

certainValue = 100;
alert(incrementByCertainValue(8)); // 输出:108

在这个例子中,函数 incrementByCertainValue 将其参数 param 与变量 certainValue 的值相加。由于 incrementByCertainValuecertainValue 都在同一个代码块中定义,函数可以访问该变量。每次调用函数时,它会读取 certainValue 的值并使用它来计算返回值。


闭包

为了将上述例子扩展为闭包,我们需要将一个(内部)函数与其词法环境中的变量绑定在一起,并通过返回这个内部函数来保存这个状态。

"use strict";

function incrementByCertainValue(param) {
  // 声明一个执行操作的函数(仅声明,暂未调用)
  const add = function (certainValue) {
    // 访问词法环境中的变量 'param' 以及其参数 'certainValue'
    return param + certainValue;
  }
  return add;
}

const incrementByFive = incrementByCertainValue(5);
alert(incrementByFive(8));  // 输出:13

const incrementBySix = incrementByCertainValue(6);
alert(incrementBySix(8));   // 输出:14

alert(incrementByFive(10)); // 输出:15

解析

外部函数 incrementByCertainValue

  1. 接受一个参数 param
  2. 定义一个内部函数 add,该函数接受另一个参数 certainValue
  3. 内部函数除了可以访问自己的参数外,还可以访问词法环境中的变量 param
  4. 返回内部函数 add

到目前为止,函数只是声明,还没有实际运行。

当在第 12 行定义变量 incrementByFive 时,它被初始化为 incrementByCertainValue(5) 的返回值。这是代码运行以及闭包技术生效的关键点。在 incrementByCertainValue 内部,参数 5 被绑定到变量 param 上。接着,创建了内部函数 add,并在其词法环境中保留了对 param 的引用。这个内部函数接受一个参数 certainValue,稍后需要调用时提供。incrementByCertainValue 的返回语句返回了内部函数 add,这个函数绑定了值 5

注意:函数名 add 是内部定义的,在 incrementByCertainValue 外部不可见。

当第二次调用 incrementByCertainValue 时,传入参数 6,它将与另一个单独的函数绑定。

运行逻辑说明

  1. 定义闭包:
    外部函数 incrementByCertainValue 的作用是创建并返回一个内部函数。这个内部函数能够访问外部函数的参数 param

  2. 保存状态:
    每次调用 incrementByCertainValue 时,传入的参数 param 都会绑定到一个新的内部函数中,并保存在闭包的词法环境里。这样,每个调用都会生成一个独立的函数实例,它们各自的 param 值互不干扰。

  3. 闭包实例:

    • 第一次调用 incrementByCertainValue(5),返回的闭包函数绑定了 param=5
    • 第二次调用 incrementByCertainValue(6),返回的闭包函数绑定了 param=6
  4. 执行闭包:
    调用 incrementByFive(8) 时:

    • 内部函数 add 读取它的词法环境中的 param=5
    • 接受调用参数 certainValue=8,计算并返回 5 + 8 = 13

    调用 incrementBySix(8) 时:

    • 内部函数 add 读取它的词法环境中的 param=6
    • 接受调用参数 certainValue=8,计算并返回 6 + 8 = 14

优势与应用场景

  1. 状态保存:
    闭包能够保存外部函数作用域中的状态变量,即使外部函数已经执行完毕。

  2. 数据隔离:
    每次创建的闭包实例是独立的,互不影响,这种特性特别适合多实例管理的场景。

  3. 回调与异步操作:
    在事件处理或异步操作中,闭包能够有效地捕获并保存上下文信息。

  4. 函数式编程:
    闭包是实现函数柯里化和高阶函数的重要工具,广泛应用于函数式编程风格中。

例如:实现一个计数器的闭包:

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter1 = createCounter();
console.log(counter1()); // 输出:1
console.log(counter1()); // 输出:2

const counter2 = createCounter();
console.log(counter2()); // 输出:1

在这个例子中,每次调用 createCounter 都会创建一个新的闭包,保存独立的 count 状态变量。

总结

闭包是 JavaScript 中强大的编程工具,能够帮助我们在复杂场景下管理数据状态、实现函数式编程,并提升代码的可读性和复用性。

Last modified: Monday, 13 January 2025, 3:07 PM