什么是闭包?

闭包(Closure)是 JavaScript 中一个非常重要的概念。简单来说,闭包就是函数和其词法环境的组合。当一个函数可以记住并访问其定义时所在的词法作用域,即使函数在其词法作用域之外执行,这就形成了闭包。

看一个简单的例子:

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

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

在上面的例子中,createCounter 返回的匿名函数可以访问外部函数的变量 count,即使 createCounter 已经执行完毕。这就是闭包。

闭包的应用场景

1. 数据私有化

闭包可以用来创建私有变量,实现数据封装:

function createPerson(name) {
  let _name = name; // 私有变量

  return {
    getName: () => _name,
    setName: (newName) => {
      if (typeof newName === 'string' && newName.length > 0) {
        _name = newName;
      }
    }
  };
}

const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
person.setName('Bob');
console.log(person.getName()); // 'Bob'
console.log(person._name); // undefined - 无法直接访问

2. 函数柯里化

闭包可以用来实现函数柯里化:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...more) {
      return curried.apply(this, args.concat(more));
    };
  };
}

const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

3. 防抖与节流

闭包在性能优化中经常用到,比如防抖和节流函数:

// 防抖
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 节流
function throttle(fn, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

闭包的陷阱

1. 内存泄漏

闭包会保持对外部作用域变量的引用,如果不当使用可能导致内存泄漏:

// 不好的例子
function createHandlers() {
  const handlers = [];
  const data = '大量的数据...';

  for (let i = 0; i < 1000; i++) {
    handlers.push(() => {
      console.log(data); // 每个函数都保持对 data 的引用
    });
  }

  return handlers;
}

解决方案:及时释放不需要的引用。

2. 循环中的闭包问题

这是经典的前端面试题:

// 错误的写法
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出: 3, 3, 3
}

// 解决方案 1: 使用 let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出: 0, 1, 2
}

// 解决方案 2: 使用闭包
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => console.log(j), 100); // 输出: 0, 1, 2
  })(i);
}

总结

闭包是 JavaScript 中强大且常用的特性:

"闭包是 JavaScript 中最优雅的特性之一,掌握它会让你的代码更加灵活和强大。"