本文由一缘原创整理,系统梳理 JS 闭包与作用域链的本质、常见应用、易错点与最佳实践,配合代码和输出,适合所有前端开发者。

深入理解 JavaScript 闭包与作用域链

闭包和作用域链是 JS 面试和实战的高频考点,也是理解 JS 内存、异步、模块化的基础。


1. 什么是作用域链?

  • JS 变量查找是“就近原则”,沿着作用域链向上查找。
  • 每个函数执行时都会生成一个作用域链。
var a = 1;
function foo() {
  var b = 2;
  function bar() {
    var c = 3;
    console.log(a, b, c);
  }
  bar();
}
foo();

输出:

1 2 3

2. 什么是闭包?

  • 闭包 = 函数 + 其引用的外部变量环境
  • 只要函数被当作值返回/传递,且引用了外部变量,就形成闭包
function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  }
}
const counter = makeCounter();
console.log(counter());
console.log(counter());

输出:

1
2

3. 闭包的常见应用

3.1. 工厂函数/私有变量

function createPerson(name) {
  let age = 18;
  return {
    getName: () => name,
    getAge: () => age,
    grow: () => age++
  };
}
const p = createPerson('Tom');
console.log(p.getName(), p.getAge());
p.grow();
console.log(p.getAge());

输出:

Tom 18
19

3.2. 循环绑定事件(经典面试题)

var btns = [];
for (var i = 0; i < 3; i++) {
  (function(i){
    btns[i] = function() { console.log(i); }
  })(i);
}
btns[0](); btns[1](); btns[2]();

输出:

0
1
2

4. 闭包的内存泄漏与注意事项

  • 闭包会导致外部变量无法被回收,注意不要滥用。
  • 事件监听、定时器等要及时清理。
function foo() {
  var big = new Array(1000000).fill(0);
  return function() { console.log(big.length); }
}
var f = foo();
// big 数组不会被回收,直到 f 释放

5. 作用域链与 this 的区别

  • 作用域链决定变量查找,this 由调用方式决定。
var x = 1;
const obj = {
  x: 2,
  getX: function() {
    return this.x;
  }
};
console.log(obj.getX());
const fn = obj.getX;
console.log(fn());

输出:

2
1

6. 闭包与异步

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}

输出:

3
3
3

正确写法:

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}

输出:

0
1
2

7. 闭包的最佳实践

  • 用 let/const 替代 var,减少闭包陷阱
  • 及时释放不再需要的闭包引用
  • 用闭包实现模块化、私有变量、工厂函数等

结语

闭包和作用域链是 JS 的灵魂,理解它们能让你写出更健壮、优雅的前端代码。欢迎留言交流更多 JS 深度问题!