闭包
闭包的定义
如果一个内层函数访问了外层函数作用域里的变量,那么即使外层函数执行完被销毁了,内层函数依然能访问到那些外层变量,这个 内层函数 + 被它捕获的外层变量 的集合就叫做 闭包。
只要一个语言支持 满足以下两个条件 的函数,就可以认为支持闭包:
- 函数可以作为参数、返回值传递,也可以赋值给变量。
- 函数可以捕获定义时所在作用域的变量,哪怕原作用域已经销毁,这些被捕获的变量依然可以被访问。
ECMAScript 与 JavaScript
ES 是 ECMAScript 的缩写,是由国际标准化组织 ECMA 国际 制定的脚本语言的通用规范标准,它不绑定任何具体的宿主环境,只规定脚本语言最核心的语法、特性、规则。
JavaScript 是 ES 规范 最知名、最主流的实现,它在 ES 核心规范的基础上,扩展了不同宿主环境的专属 API:除了完全实现 ES 的所有标准特性外,JS 还会根据运行环境的不同,额外提供环境相关的能力。
两者的关系是 先有 JS 实现,后有 ES 标准,标准是基于 JS 制定的。
闭包的产生条件
只要同时满足以下3点,闭包就会 自动产生,不需要任何特殊语法:
- 函数嵌套:存在内层函数和外层函数,内层函数定义在外层函数的作用域内。
- 内层函数访问外层作用域的变量:内层函数引用了外层函数(或更上层作用域)中的变量。
- 内层函数被拿到外层作用域执行:内层函数被作为返回值返回、赋值给外部变量、或者作为参数传递到外层作用域,最终在它定义的作用域之外执行。
闭包的核心是 访问定义时的父作用域变量,如果函数定义在全局作用域,父作用域就是全局,本身就是全局可访问的,不存在闭包的概念。
闭包的存在形式
1. 外层函数返回内层函数,同时保留对上层变量的引用
1 | function outer() { |
2. 把内层函数传递到外层作用域执行,就会形成闭包
1 | function outer() { |
setTimeout 的回调函数是在 全局执行上下文 中被执行的。
3. 高阶函数封装创建闭包
防抖、节流、柯里化这类高阶函数,本质都是通过闭包保留状态。
1 | // 柯里化求和函数,通过闭包保留已传入的参数 |
类似的场景还有事件绑定、接口请求回调等,只要 回调函数定义在某个作用域内,并且访问了该作用域的变量,就会形成闭包。
注意事项
闭包的核心是 保留外层作用域的变量,如果使用不当会导致内存泄漏。不需要用到闭包的时候及时解除引用,比如事件解绑、把闭包变量赋值为null,让垃圾回收可以正常清理。
被闭包引用的变量不会被垃圾回收机制回收,如果闭包一直存在,这些变量就会一直占着内存。











