闭包是什么?为什么闭包会造成内存泄漏?
很多文章和书籍解释闭包时都和函数划等号,之前我也一直理解闭包就是一个可以访问外部环境变量的函数,JS 中的所有函数都是闭包。
但我觉得这种函数就是闭包、闭包就是函数的说法让闭包的概念更加模糊,难以理解。在 Rust 语言中,闭包是可以捕获其所在环境的匿名函数,其功能和普通函数是不同的,我们可以根据需要选择使用闭包还是普通函数,这种函数与闭包的差异让闭包理解起来更加简单。
根据 MDN 对闭包的解释,我是这样理解的:在 JS 中,闭包是一个组合,由函数及其声明时的词法环境组成,即函数绑定了一些外部状态的引用。这使得内部函数可以访问外部函数的作用域,即便外部函数已经被销毁了。
要理解闭包是如何访问外部环境,需要先了解下闭包作用域链。
作用域 Scope
作用域是当前执行上下文,其中的值和表达式是“可见”的或可以被引用的。JS有以下几种作用域:
- Block scope: The scope created with a pair of curly braces
- Function scope: The scope created with a function.
- Local scope
- Closure scope
- Module scope: The scope for code running in module mode.
- Global scope: The default scope for all code running in script mode.
debugger 会将 script 顶层声明的变量显示为 “Script scope”,这只是为了帮助区分全局变量,本质上还是属于“Global scope”。
闭包作用域链 Closure scope chain
作用域链的形成是基于 JavaScript 采用的词法作用域(Lexical Scope)机制,JavaScript 引擎在编译阶段就确定了变量和函数的作用域。这些作用域构成的作用域链保证了当前执行环境对符合访问权限的变量的有序访问。
闭包和内存泄漏
每一个函数在编译时会生成一个 Closure
对象,分析函数中声明的��部函数使用了函数词法环境中的哪些变量,然后将这些变量的引用加入 Closure
对象,最终这个闭包对象将作为这些内部函数[[Scopes]]
属性(即作用域链)中的一员。
闭包产生内存泄漏的根本原因是因为 Closure
被其所有内部函数作用域链引用,只要有一个内部函数没有销毁,Closure
就无法销毁,导致其引用的变量也无法销毁,最终产生了内存泄漏。
function foo(){
let arr = Array(10000000)
function foo1(){
console.log(arr)
}
return function foo2(){}
}
window.bar = foo()
// window.bar --> foo2 --> foo.Closure --> arr
上面这段代码中
- 编译阶段:因为
foo1
使用了arr
,所以foo.Closure
引用了arr
; - 代码执行:
window.bar
持有了foo2
,foo2
的作用域链包含foo.Closure
,foo.Closure
引用了arr
,最终导致arr
无法释放产生内存泄漏。
相关资料:
转载自:https://juejin.cn/post/7386701590268428297