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

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

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

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

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

常见错误现象:

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

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

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 绑定,却忽略另一个更隐蔽的问题:依赖的局部变量可能已变更或销毁。

典型场景:

  • 组件卸载后,异步回调仍尝试更新已销毁的 DOM 或 React 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,而不是靠外部变量管理——这是健壮性的关键。

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


# react  # javascript  # es6  # java  # js  # node.js  # json  # node  # 作用域  # 为什么  # count  # 封装  # Token  # 局部变量  # 循环  # class  # private  # var  # 闭包 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】  焦点电影公司作品,电影焦点结局是什么?  什么是javascript作用域_全局和局部作用域有什么区别?  实例解析angularjs的filter过滤器  java ZXing生成二维码及条码实例分享  Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程  Laravel如何使用Collections进行数据处理?(实用方法示例)  Android滚轮选择时间控件使用详解  教学论文网站制作软件有哪些,写论文用什么软件 ?  Laravel怎么实现搜索高亮功能_Laravel结合Scout与Algolia全文检索【实战】  JavaScript如何实现倒计时_时间函数如何精确控制  Laravel如何使用Passport实现OAuth2?(完整配置步骤)  香港网站服务器数量如何影响SEO优化效果?  米侠浏览器网页图片不显示怎么办 米侠图片加载修复  电商网站制作价格怎么算,网上拍卖流程以及规则?  为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】  学生网站制作软件,一个12岁的学生写小说,应该去什么样的网站?  网站制作企业,网站的banner和导航栏是指什么?  如何快速搭建自助建站会员专属系统?  微信小程序制作网站有哪些,微信小程序需要做网站吗?  jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】  Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】  Linux系统命令中tree命令详解  JS去除重复并统计数量的实现方法  猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?  Python数据仓库与ETL构建实战_Airflow调度流程详解  如何将凡科建站内容保存为本地文件?  Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】  Swift中swift中的switch 语句  Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程  JavaScript如何实现错误处理_try...catch如何捕获异常?  Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】  实现点击下箭头变上箭头来回切换的两种方法【推荐】  广州网站制作公司哪家好一点,广州欧莱雅百库网络科技有限公司官网?  Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】  PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)  在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?  高端云建站费用究竟需要多少预算?  php485函数参数是什么意思_php485各参数详细说明【介绍】  如何在Windows服务器上快速搭建网站?  如何选择PHP开源工具快速搭建网站?  如何在建站宝盒中设置产品搜索功能?  宙斯浏览器怎么屏蔽图片浏览 节省手机流量使用设置方法  如何快速选择适合个人网站的云服务器配置?  javascript中的数组方法有哪些_如何利用数组方法简化数据处理  如何用免费手机建站系统零基础打造专业网站?  如何自定义建站之星网站的导航菜单样式?  如何在建站之星网店版论坛获取技术支持?  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?