再谈闭包草稿
# 前言
闭包很简单,一个函数与创建该函数的词法环境,就是一个闭包,该词法环境包含了闭包所能访问的所有局部变量
闭包又很难,外部词法环境被销毁,而自身又保持对外部词法环境变量的引用,执行引擎对堆栈的处理是怎样的
本文将对以下几个问题进行解析
- 词法环境
- 闭包中保存的引用是什么
# 词法环境
函数声明和匿名函数表达式不会创建新的变量环境,其变量环境指向函数创建时的变量环境
命名函数表达式会创建一个新的变量环境,新的变量环境 outer 指向旧变量环境,环境记录指向所赋值的变量
每个大括号会生成一个词法环境,所以 for 循环中 let 定义的 i 属于每个词法环境的元素
词法环境包括:
- 环境记录
- 对外部的引用
环境记录初始化是在定义时(代码执行前)做的,扫描代码并把变量声明记录到环境记录中,所以声明函数时,里面 let 重复定义在声明时就报错了
同时初始化的时候会设置外部的引用,指向上一层的词法环境
https://juejin.im/user/5757d748207703006fd9deb2
# 闭包
闭包:把闭包函数放入了执行调用栈,生成闭包函数的函数在生成闭包函数后就弹出执行栈了,闭包函数内对上层函数的访问其实是对内存中元素的访问,所以生成闭包的函数不能有其他地方的引用,否则会造成垃圾回收无法回收
function test(){
let a = 1
return function(flag){
if(flag){
console.log(a)
}
}
}
console.dir(test())
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
function test(){
let a = 1
setTimeout(function(){
console.log(a)
})
}
test()
1
2
3
4
5
6
7
2
3
4
5
6
7
创建setTimeout函数时,内部有对外部词法环境属性的引用,但是都是属于同个词法环境
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i)
})
}
1
2
3
4
5
2
3
4
5
# 垃圾回收
由于某个闭包变量被使用,导致上层词法环境没有被回收,这是因为返回的函数中会挂载上 [[scopes]].closure
外部词法环境引用
浏览器开启内存变化工具查看
function test(){
let a = Array(100000).fill(0)
return function(){
a = null
}
}
let s = test()
console.dir(s)
s()
console.dir(s)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
先升高,解除引用后,内存降低
但是 v8 引擎有做优化。。其实没那么简单。。,没有显示的使用,不会去创建这个 a 数组
# 拓展阅读
https://blog.csdn.net/weixin_40013817/article/details/103287271
编辑 (opens new window)
上次更新: 2023/08/23, 09:32:05