JavaScript - 闭包

Himmelbleu / 2024-08-30 / 原文

闭包概念

闭包(Closure)是 JavaScript 中的一个核心概念,它指的是函数和其外部环境(或词法作用域)之间的组合。简单来说,闭包是指当一个函数在其外部作用域中引用了变量时,该函数和这些变量的组合形成了一个闭包

闭包的表现

闭包通常是由一个外部函数包裹一个内部函数,且返回内部函数,从而使得内部函数可以访问外部函数作用域中的变量。但这并不是闭包的唯一形式。

闭包的本质是 函数“记住”了它所在的词法作用域,即使这个函数是在其定义的作用域之外执行的。

常见的闭包

function outerFunction() {
    let count = 0;
    return function innerFunction() {
        count++;
        console.log(count);
    };
}

const closure = outerFunction();
closure(); // 输出 1
closure(); // 输出 2

不返回函数的闭包

即使外部函数不返回内部函数,闭包仍然存在,只要内部函数访问了外部函数中的变量。例如,在事件监听器或定时器中。

function setupClickListener() {
    let count = 0;
    document.getElementById('myButton').addEventListener('click', function() {
        count++;
        console.log(count); // 访问了外部函数的变量 count
    });
}

setupClickListener();

虽然 setupClickListener 没有返回内部函数,但内部函数仍然形成了闭包,因为它引用了外部函数中的 count 变量。

定时器中的闭包

function startTimer() {
    let count = 0;
    setInterval(function() {
        count++;
        console.log(count); // 访问外部函数的 count
    }, 1000);
}

startTimer();

使用场景

数据封装

闭包允许创建私有变量,这些变量在函数外部无法直接访问。通过闭包,可以创建具有私有状态的对象,从而实现数据封装。例如:

function createCounter() {
  let count = 0; // count 是私有变量
  return function () {
    count++;
    return count;
  };
}

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

const counter2 = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

闭包可以用来创建模块化的代码结构,使得模块内的变量和函数不会污染全局命名空间。例如:

const Module = (function () {
  let privateVar = "I am private";

  function privateMethod() {
    console.log(privateVar);
  }

  return {
    publicMethod: function () {
      privateMethod();
    }
  };
})();

Module.publicMethod(); // 输出 "I am private"

闭包的关键在于它能够“记住”外部函数的状态,即使外部函数已经执行完毕。这个特性使得闭包在 JavaScript 中非常有用,尤其是在涉及到数据隐私、状态管理和函数工厂等方面。

延迟执行

闭包可以用来延迟执行某些代码,尤其在处理异步操作时。例如:

function delayedGreeting(name) {
  return function () {
    console.log(`Hello, ${name}!`);
  };
}

const greetJohn = delayedGreeting("John");
setTimeout(greetJohn, 1000); // 1秒后输出 "Hello, John!"

事件处理

闭包常用于事件处理程序,以便访问事件处理程序内的数据。例如:

function setupButton(buttonId) {
  const button = document.getElementById(buttonId);
  let count = 0;

  button.addEventListener("click", function () {
    count++;
    console.log(`Button clicked ${count} times`);
  });
}

setupButton("myButton");

内存泄漏

闭包可以导致内存泄漏,因为闭包会保留对其外部作用域的引用。如果闭包引用的变量或函数没有被及时释放,垃圾回收机制可能不会回收这些内存,从而导致内存泄漏。

例如,在事件监听器中使用闭包,如果不及时移除事件监听器,可能会导致内存无法被释放:

function createListener() {
    let element = document.getElementById('myElement');
    element.addEventListener('click', function() {
        console.log('Clicked!');
    });
}
createListener();

这个例子中,如果不手动移除事件监听器,即使元素被删除了,闭包仍然会保留对它的引用,导致内存泄漏。

避免过度依赖闭包来保存状态

可以通过将状态和逻辑分离来减少闭包的使用。例如,使用对象、类或者数据结构来管理状态,而不是全部放入闭包中。

class Counter {
    constructor() {
        this.count = 0;
    }

    increment() {
        this.count++;
        return this.count;
    }
}

const counter = new Counter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2