一篇文章带你透彻了解js中的闭包概念

38 阅读4分钟

闭包

首先很多人一看到这个词都会想,闭包是什么,它为什么会这么定义,简单来说,闭包是指一个内部嵌入函数可以访问其外部作用域的变量。

在 JavaScript 中,变量的作用域由函数界定(即 “函数作用域”)。通常函数执行结束后,其内部作用域会被系统清理,相关变量也会被垃圾回收以释放内存。但闭包是特殊情况 —— 它是定义在一个函数(外层函数)内部的子函数,关键在于会引用外层函数的变量或参数。

正因为这种引用关系,即便外层函数已经执行完毕,它的作用域也不会被销毁(毕竟闭包还需要访问其中的变量)。此时这个内层函数(闭包)就相当于 “保留” 了对上级作用域的访问权限,外层作用域里的变量也会一直存在于内存中,不会随函数执行结束而消失。

说到底,闭包的形成,核心是和变量的作用域规则、以及变量的生存周期这两个因素紧密绑定的 —— 三者相互关联,才让闭包拥有了 “持久访问外层变量” 的特性。

1. 变量的作用域

本质就是变量能被访问的有效范围。代码执行时,会顺着执行环境生成的作用域链逐层向外查找变量,直到找到全局对象才会停止。

2. 变量的生存周期

全局变量的生存周期是永久的,除非主动对其进行销毁操作;而函数作用域内的变量,或是用 let、const 声明的局部变量,通常会在函数执行完毕后,随着作用域的销毁而被回收。但如果函数内部的子函数(闭包)引用了外层变量,这类变量的回收就会遵循引用计数等垃圾回收机制 —— 只要闭包还在引用,变量就不会被销毁。

3. 内存泄漏相关

在 IE 浏览器中,JavaScript 对象和 DOM 对象的垃圾收集机制并不相同。正因为这种差异,闭包在 IE 中容易引发内存泄漏问题,具体表现为原本该被销毁的 DOM 元素,会因为闭包的引用而一直占用内存,无法正常释放。

闭包核心总结

1. 概念

函数嵌套时,内层函数引用外层变量形成的特殊结构,可跨作用域访问变量。

2. 作用

  • 实现变量私有化,避免全局污染;
  • 延长变量生命周期,持久化状态;
  • 封装逻辑,支持模块化开发。

3. 优点

  • 灵活保留函数执行上下文;
  • 无需全局变量即可共享数据;
  • 简化代码模块化实现。

4. 缺点

  • 占用额外内存,滥用易导致内存溢出;
  • IE 浏览器中可能引发 DOM 相关内存泄漏。

闭包常见场景

1. 场景:实现计数器(需持久化计数状态,不污染全局)

javascript

运行

function createCounter() {
  let count = 0; // 私有变量
  return () => count++; // 闭包:引用外层count
}
const add = createCounter();
console.log(add()); // 1
console.log(add()); // 2

2. 场景:缓存计算结果(避免重复运算,提升效率)

javascript

运行

function createCache() {
  const cache = {}; // 缓存容器
  return (key, val) => {
    if (cache[key]) return cache[key];
    cache[key] = val; // 闭包保留cache状态
    return val;
  };
}
const setCache = createCache();
setCache("name", "张三");
console.log(setCache("name")); // 直接读取缓存:张三

3. 场景:模块化封装(隐藏私有逻辑,只暴露公共接口)

javascript

运行

const userModule = (function() {
  const password = "123456"; // 私有变量
  return {
    checkPwd: (pwd) => pwd === password // 闭包访问password
  };
})();
console.log(userModule.checkPwd("123456")); // true
console.log(userModule.password); // undefined(无法直接访问)

高频面试题

面试题 1 var 作用域 + 闭包共享变量

javascript

运行

var arr = [];
for (var i = 0; i < 3; i++) {
  arr[i] = function() { console.log(i); };
}
arr[0](); // 结果:3
arr[1](); // 结果:3

解析:var 声明的 i 是全局作用域,循环结束后 i=3;数组内函数是闭包,共享同一个 i。

面试题 2 闭包实现变量私有化

javascript

运行

function Person() {
  var name = "张三";
  this.getName = () => name;
  this.setName = (n) => name = n;
}
var p = new Person();
p.name; // 结果:undefined
p.getName(); // 结果:张三
p.setName("李四");
p.getName(); // 结果:李四

解析:name 是私有变量,外部无法直接访问;getName/setName 是闭包,可访问 name。

面试题 3 闭包的独立作用域

javascript

运行

function fn() {
  var num = 10;
  return () => { num++; console.log(num); };
}
var f1 = fn();
var f2 = fn();
f1(); // 结果:11
f1(); // 结果:12
f2(); // 结果:11

解析:每次调用 fn () 创建独立作用域,f1/f2 对应不同的 num。

ps: 非常感谢大家能够看到最后,祝大家可以在前端的学习下共同成长,一起变得更优秀!!!