FPF   付鹏篚的笔记
  • 主页
  • 归档
  • 分类
  • 标签
  • 关于

付鹏篚

  • 主页
  • 归档
  • 分类
  • 标签
  • 关于
Quiet主题
  • JavaScript

闭包 (Closure)

付鹏篚
JavaScript

2021-01-11 20:00:00

文章目录
  1. 一、什么是闭包 (Closure)
  2. 二、闭包的形成
  3. 三、闭包的特点
  4. 四、闭包的例子
    1. 1. 函数嵌套形成闭包
    2. 2. 闭包与返回函数
    3. 3. 闭包与循环
    4. 4. 闭包与及时调用函数表达式 (IIFE)
  5. 五、闭包的应用场景
    1. 1. 数据封装和私有变量
    2. 2. 函数记忆化(Memoization)
  6. 六、闭包的注意事项
  7. 七、总结

一、什么是闭包 (Closure)

闭包是 JavaScript 中的一个重要概念,指的是函数可以“记住”并访问定义时的作用域,而不管函数是在何处执行。简而言之,闭包是指一个函数可以访问其外部函数的变量和参数,即使外部函数已经执行完毕。

二、闭包的形成

闭包的形成通常有以下两种情况:

  1. 函数嵌套: 当一个函数内部定义另一个函数时,内层函数就形成了闭包。内层函数能够访问外层函数的变量。
  2. 返回函数: 当一个函数返回另一个函数,并且返回的函数使用了外层函数的变量时,也会形成闭包。

三、闭包的特点

  1. 作用域链: 闭包会“记住”其创建时的作用域,因此即使外层函数已经执行完,内层函数仍然能够访问外层函数的变量。
  2. 变量持久化: 闭包能够保存外部函数的局部变量,使得外部函数的变量不会随着函数执行完毕而被销毁。

四、闭包的例子

1. 函数嵌套形成闭包

function outer() {
  let outerVar = 'I am from outer!';
  
  function inner() {
    console.log(outerVar);  // 访问外层函数的变量
  }
  
  return inner;
}

const closureFunc = outer();  // outer 执行完毕,inner 返回并形成闭包
closureFunc();  // 输出 "I am from outer!"

在这个例子中,inner 函数访问了 outer 函数的变量 outerVar,即使 outer 函数已经执行完毕,inner 依然能够访问 outerVar,这就是闭包的典型示例。

2. 闭包与返回函数

function makeCounter() {
  let count = 0;  // 外部变量
  
  return function() {
    count++;
    console.log(count);  // 闭包访问并修改外部变量
  };
}

const counter1 = makeCounter();
counter1();  // 输出 1
counter1();  // 输出 2

const counter2 = makeCounter();
counter2();  // 输出 1
counter1();  // 输出 3

在这个例子中,makeCounter 函数返回一个内部函数,该内部函数每次调用时会修改外部变量 count 的值。每次调用 makeCounter 时,都会创建一个新的闭包,所以 counter1 和 counter2 的 count 是独立的,互不影响。

3. 闭包与循环

闭包经常被用来处理循环中的异步操作,避免了常见的“最后一个值”的问题。

function createFunctions() {
  let funcs = [];
  
  for (var i = 0; i < 3; i++) {
    funcs[i] = function() {
      console.log(i);  // 访问 i
    };
  }
  
  return funcs;
}

const funcs = createFunctions();
funcs[0]();  // 输出 3
funcs[1]();  // 输出 3
funcs[2]();  // 输出 3

在这个例子中,由于 var 是函数作用域,i 的值在循环结束后是 3,因此 funcs 数组中的每个函数都会输出 3。

4. 闭包与及时调用函数表达式 (IIFE)

IIFE(Immediately Invoked Function Expression)常常用来创建局部作用域,避免污染全局作用域。

(function() {
  let privateVar = 'This is private!';
  console.log(privateVar);  // 可以访问 privateVar
})();

console.log(privateVar);  // 报错:privateVar is not defined

在这个例子中,IIFE 创建了一个局部作用域,privateVar 只在该作用域内有效,外部无法访问。

五、闭包的应用场景

1. 数据封装和私有变量

闭包可以用来模拟私有变量,使得外部无法直接访问和修改这些变量,从而实现数据封装。

function Counter() {
  let count = 0;  // 私有变量
  
  this.increment = function() {
    count++;
    console.log(count);
  };
  
  this.decrement = function() {
    count--;
    console.log(count);
  };
}

const counter = new Counter();
counter.increment();  // 输出 1
counter.increment();  // 输出 2
counter.decrement();  // 输出 1
console.log(counter.count);  // undefined,无法访问 count

在这个例子中,count 是私有变量,外部无法直接修改它,只能通过 increment 和 decrement 方法来操作。

2. 函数记忆化(Memoization)

闭包可以用来缓存函数的计算结果,从而提高性能,避免重复计算。

function memoize(fn) {
  const cache = {};
  return function(n) {
    if (n in cache) {
      return cache[n];  // 返回缓存值
    }
    const result = fn(n);
    cache[n] = result;  // 缓存结果
    return result;
  };
}

const factorial = memoize(function(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
});

console.log(factorial(5));  // 120
console.log(factorial(5));  // 120,直接从缓存中返回

在这个例子中,memoize 函数使用闭包保存了一个 cache 对象,缓存了函数的计算结果,从而避免了重复计算。

六、闭包的注意事项

  1. 内存泄漏: 由于闭包会保持对外部作用域变量的引用,如果闭包不再需要但没有被正确清理,可能会导致内存泄漏。应当注意避免无用的闭包引用。
  2. 性能问题: 过多使用闭包可能会导致性能问题,特别是在大量创建闭包的情况下。可以适当地减少闭包的使用。

七、总结

闭包是 JavaScript 中强大的功能,允许函数访问并操作外部函数的变量,即使外部函数已经执行完毕。它广泛应用于数据封装、私有变量模拟、函数记忆化等场景。理解闭包的工作原理,能够帮助我们更好地利用 JavaScript 进行开发。

上一篇

VXE Table

下一篇

原型链

©2025 By 付鹏篚. 主题:Quiet
Quiet主题