JavaScript的闭包是什么_它有什么实际用途【教程】

6次阅读

闭包javaScript中函数作用域与词法环境自然结合的必然结果;其形成依赖函数的[[Environment]]属性和自由变量查找机制,用于封装私有状态、保持异步上下文,并需警惕内存泄漏。

JavaScript的闭包是什么_它有什么实际用途【教程】

闭包不是语法糖,也不是高级技巧——它是 javascript 中函数作用域和词法环境自然结合的必然结果。只要一个函数在定义它的词法作用域之外被调用,且它引用了该作用域中的变量,闭包就产生了。

闭包是怎么形成的:看懂 [[Environment]] 和自由变量

每个函数对象内部都有一个隐藏属性 [[Environment]],它指向函数定义时所在的作用域链。当函数执行时,如果访问了自己作用域中未声明的变量(即“自由变量”),引擎就会顺着 [[Environment]] 一层层向上查找——这个被保留下来的外层词法环境,就是闭包的核心。

常见错误现象:

  • 循环中用 var 声明变量 + 异步回调,所有回调都输出最后一个值(因为共享同一个变量绑定)
  • 误以为 setTimeout 的回调“捕获了当前值”,其实捕获的是变量引用

正确做法是让每次迭代拥有独立作用域:

立即学习Java免费学习笔记(深入)”;

for (var i = 0; i < 3; i++) {   (function(i) {     setTimeout(() => console.log(i), 100);   })(i); } // 或更现代的写法: for (let i = 0; i < 3; i++) {   setTimeout(() => console.log(i), 100); }

let 每次迭代都会创建新绑定,[[Environment]] 指向各自独立的块级环境,本质仍是闭包。

封装私有状态:用闭包替代 class 私有字段(尤其兼容旧环境)

es6 class#private 字段在 IE 和部分老版本 node.js 中不可用,而闭包在任何支持函数的一版 js 中都有效。

使用场景:

  • 模块导出 API 时隐藏实现细节(如缓存、计数器、配置)
  • 避免全局污染或意外修改关键状态
  • 需要精细控制 getter/setter 行为(比如带校验的 setter)

示例:

function createcounter() {   let count = 0; // 外部无法直接访问   return {     increment() { count++; },     get value() { return count; },     reset() { count = 0; }   }; }  const counter = createCounter(); counter.increment(); console.log(counter.value); // 1 console.log(counter.count); // undefined —— 真正私有

注意:这里返回的对象方法都闭包了 count,但对象本身没有暴露该变量。

事件监听与定时器中保持上下文:为什么 this 不是唯一问题

很多人只记得用 .bind() 或箭头函数解决 this 绑定,却忽略另一个更隐蔽的问题:依赖的局部变量可能已变更或销毁。

典型场景:

  • 组件卸载后,异步回调仍尝试更新已销毁的 domreact state
  • 轮询请求中,用户切换页面,旧的 idToken 仍被后续响应使用

闭包能帮你“冻结”那一刻所需的最小数据集:

function startPolling(id, token) {   const poll = () => {     fetch(`/api/data?id=${id}`, {       headers: { Authorization: `Bearer ${token}` }     }).then(r => r.json())       .then(data => updateUI(data));   };   const timer = setInterval(poll, 5000);    // 返回清理函数,利用闭包捕获 timer 和 id   return () => clearInterval(timer); }  const stop = startPolling(123, 'abc'); // 后续可安全调用 stop(),无需再传参数

这里 stop 函数闭包了 timer,而不是靠外部变量管理——这是健壮性的关键。

闭包真正的复杂点不在“怎么写”,而在“什么时候不该用”:过度闭包会阻止垃圾回收,导致内存泄漏;在频繁创建函数的循环里(如渲染大量列表项),每个闭包都携带一份环境引用,开销比想象中大。判断依据很简单——只闭包真正需要的数据,其余尽量通过参数传入。

text=ZqhQzanResources