闭包学了好多次, 始终很迷
今天我觉得我理解了一些,从例子开始
问:输出什么
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
答案
当然是 5 个 5
记得初学js的我,心中的答案是 0,1,2,3,4
原因
首先 变量i
是通过var
定义的,es6
之前只有函数作用域,通过var
定义的变量会被提升到作用域的顶端,我理解就是提升到函数的第一行, 就像下面这样
function() {
console.log(ui)
var ui = 24
}
等价于
function() {
var ui = 24
console.log(ui)
}
回到主题,setTimeout
的执行机制是: 当遇到 setTimeout
函数,就会在指定时间之后(也就是第二个参数)把回调函数放入任务队列,等到主线程执行栈为空的时候,再依次执行任务队列里的任务,
当 for
循环执行完毕 i
已经变成了 5
, 因此当执行到任务队列中的任务的时候,i
就是5
,所以打印出 5 个 5
如何打印 0,1,2,3,4
最简单的方式
最简单的方式当然是 把 var
改成 let
了,由于是块级作用域,所以在每次循环的时候, console.log(i)
中的i
保持的对 let i
的引用,因此它暂时不会被垃圾回收机制处理,就能在执行到任务队列的时候沿着执行上下文找到i
关于垃圾回收机制暂时不是很懂,只是知道 1. 如果变量被其他变量引用,那么就会一直保留在内存空间中 2. 如果函数执行完毕且函数内部的变量没有被函数外引用,那么函数内的变量会被垃圾回收处理
闭包方式
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i)
}, 1000)
})(i)
}
是不是发现i
有点多,实际上是这样的,
我们在for循环
内部写了一个立即执行函数
,
还记得刚才提到的函数作用域吗, 通过立即执行函数
我们创建里一个新的作用域, 当 i
传递到函数内部的时候,console.log(i)
会保持着对函数形参
的引用, 所以函数形参 i
,不会被立刻垃圾回收,而是被保留在了当前作用域下
因此当执行到任务队列的时候,会打印出 0,1,2,3,4
, 这时才进入垃圾回收机制环节
可是这和闭包有什么关系???
通过打断点,可以看到 产生了 闭包 Closure
因为我们在立即执行函数
作用域下,访问了其他作用域的变量,那么当读取到这个变量的时候,就会产生闭包
重新理解闭包
在红宝书第四版 第10.14章节提到
闭包是有权限访问其他函数作用域内的变量的一个函数
这里涉及作用域与作用域链 和 内存回收机制 等知识点
当函数执行完毕, 函数内的活动对象(变量)会被销毁, 但是由于作用于链, 它可以访问上一级作用域的变量, 因此即使上一级的函数执行完毕, 作用域也不会随之销毁, 这时子函数-也就是闭包, 就拥有了访问父级函数作用域的权限, 即使父级函数执行完毕
根据词法作用域的规则,内部函数总是可以访问外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束,但是内部函数引用着外部函数的变量依然保存与内存中,这些变量的集合称为闭包。
闭包的使用场景
- 事件绑定的回调方法
setTimeout
的延迟回调- 调用函数, 内部返回一个函数
持续学习闭包中......