闭包

闭包是JavaScript中的一个重要概念,它允许函数访问并操作其词法作用域之外的变量。

基本概念

闭包是指有权访问另一个函数作用域中变量的函数:

javascript
function outerFunction() {
  const outerVariable = '外部变量';
  
  function innerFunction() {
    console.log(outerVariable); // 可以访问外部变量
  }
  
  return innerFunction;
}

const inner = outerFunction();
inner(); // 输出: 外部变量

在这个例子中,innerFunction是一个闭包,它可以访问outerFunction作用域中的outerVariable变量,即使outerFunction已经执行完毕。

闭包的工作原理

词法作用域

JavaScript使用词法作用域,也就是说,函数的作用域在函数定义时就确定了,而不是在函数调用时。

作用域链

当访问一个变量时,JavaScript会首先在当前函数作用域中查找,如果找不到,就会向上查找外层函数的作用域,直到找到该变量或到达全局作用域。

闭包的形成

当一个函数返回另一个函数时,返回的函数会保留对其词法作用域的引用,这就形成了闭包。

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

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

const counter2 = makeCounter();
console.log(counter2()); // 1(独立的闭包)

闭包的应用场景

1. 数据封装和私有变量

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

javascript
function createPerson(name, age) {
  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return age;
    },
    setAge: function(newAge) {
      if (newAge > 0 && newAge < 150) {
        age = newAge;
      }
    }
  };
}

const person = createPerson('John', 30);
console.log(person.getName()); // John
console.log(person.getAge()); // 30
person.setAge(35);
console.log(person.getAge()); // 35

// 无法直接访问name和age
console.log(person.name); // undefined

2. 函数工厂

使用闭包创建具有相似功能的函数:

javascript
function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

3. 事件处理

在事件处理中使用闭包保存状态:

javascript
for (var i = 0; i < 5; i++) {
  (function(index) {
    const button = document.createElement('button');
    button.textContent = '按钮 ' + index;
    button.addEventListener('click', function() {
      console.log('点击了按钮 ' + index);
    });
    document.body.appendChild(button);
  })(i); // 立即执行函数,创建闭包
}

4. 模块模式

使用闭包实现模块化,避免全局命名冲突:

javascript
const counterModule = (function() {
  let count = 0;
  
  function increment() {
    count++;
  }
  
  function decrement() {
    count--;
  }
  
  function getCount() {
    return count;
  }
  
  return {
    increment,
    decrement,
    getCount
  };
})();

counterModule.increment();
counterModule.increment();
console.log(counterModule.getCount()); // 2

5. 记忆化

使用闭包缓存函数结果,提高性能:

javascript
function memoize(func) {
  const cache = {};
  
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = func.apply(this, args);
    cache[key] = result;
    return result;
  };
}

const slowFunction = function(n) {
  console.log('计算...');
  let result = 0;
  for (let i = 0; i < n; i++) {
    result += i;
  }
  return result;
};

const fastFunction = memoize(slowFunction);
console.log(fastFunction(1000)); // 计算... 499500
console.log(fastFunction(1000)); // 499500(从缓存获取)

闭包的注意事项

内存泄漏

闭包会保留对外部变量的引用,如果闭包被长期保存,可能会导致内存泄漏:

javascript
function createElement() {
  const element = document.createElement('div');
  
  // 闭包引用element,即使DOM中删除了element,也不会被垃圾回收
  element.addEventListener('click', function() {
    console.log('元素被点击');
  });
  
  return element;
}

const div = createElement();
document.body.appendChild(div);
// ... 之后删除元素
document.body.removeChild(div);
// 但闭包仍然引用div,可能导致内存泄漏

解决方法:

javascript
function createElement() {
  const element = document.createElement('div');
  
  function handleClick() {
    console.log('元素被点击');
  }
  
  element.addEventListener('click', handleClick);
  
  // 提供清理函数
  element.removeEventListener = function() {
    element.removeEventListener('click', handleClick);
  };
  
  return element;
}

循环中的闭包问题

在使用var声明变量的循环中,闭包可能会导致意外行为:

javascript
// 问题代码
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i); // 输出5次5
  }, 1000);
}

解决方法:

  1. 使用let声明变量(ES6+):
javascript
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i); // 输出0, 1, 2, 3, 4
  }, 1000);
}
  1. 使用立即执行函数:
javascript
for (var i = 0; i < 5; i++) {
  (function(index) {
    setTimeout(function() {
      console.log(index); // 输出0, 1, 2, 3, 4
    }, 1000);
  })(i);
}

闭包与this

在闭包中使用this时需要注意,this的值取决于函数的调用方式:

javascript
const obj = {
  name: 'John',
  sayName: function() {
    return function() {
      console.log(this.name); // this指向全局对象
    };
  }
};

obj.sayName()(); // 输出: undefined(在严格模式下)

解决方法:

  1. 使用箭头函数(继承外部作用域的this):
javascript
const obj = {
  name: 'John',
  sayName: function() {
    return () => {
      console.log(this.name); // this指向obj
    };
  }
};

obj.sayName()(); // 输出: John
  1. 保存this到变量:
javascript
const obj = {
  name: 'John',
  sayName: function() {
    const self = this;
    return function() {
      console.log(self.name); // 使用保存的self
    };
  }
};

obj.sayName()(); // 输出: John

学习资源


继续学习:作用域与提升

最后更新:2026-02-08