今天了解了一下js闭包这块的内容,还是有点诡异的,将实践结果记录一下,看完只后,我敢说,闭包就那么回事,所谓的闭包,其实就是客户端开发中,其实就是叫做内存泄漏,就是不当引用导致对象没法得到释放,哈哈,玩笑开得有点过了,只是有点像哈,其实并不全是。
代码语言:javascript复制for(var i=1;i<=5;i ){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
你能说出,这个写法将会输出什么吗?先不要看下面的答案,自己凭自己的经验说一下。
据说至少一半的初学者会回答1,2,3,4,6
等等,那个8903是个什么鬼,其实那个8903是setTimeout函数的返回值,本来应该打印5个出来的,如下图所示:
但是为什么只打印了1个出来呢,不是应该打印5个吗?这个问题,也许你没有思考过,然而,我以前也没有思考过,但是今天我通过实验弄清楚了由来。看下面两个实验。
所以,我们知道,我们把代码贴到console控制台上去执行的时候,实际上基本上等价于。
代码语言:javascript复制console.log(eval(`let ret = 0
let fun = function () {
return ret
}
for (var i = 1; i <= 5; i ) {
fun()
}`))
而,我们知道,eval返回值的规则,如果你不知道,可以在这里了解eval() - JavaScript | MDN
因为,回到我们的最初代码
代码语言:javascript复制for(var i=1;i<=5;i ){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
这里打印了一个8903就不难理解了。但是,我想说的是,这个说了这么多,只是一个插曲而已,本文的重点不是这个,就上述这段代码,我们的本意是想让它打印1.2.3.4.5的,结果你给我5个6。那么,改为下面这个方式呢?
代码语言:javascript复制for (var i = 1; i <= 5; i ) {
(function () {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
})()
}
使用一个IIFE将其包裹起来,那么实际的执行结果将会符合我们的预期吗?下不要看下面的答案,同样,你自己感受一下。
没错,同样的道理,并不符合我们的预期。
继续改,看下面
代码语言:javascript复制for (var i = 1; i <= 5; i ) {
var j = i;
(function () {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})()
}
可以看到,效果确实和上一次差不多,但是为什么输出的是5个5呢?那是因为,最后一次i 影响不到j了,所以是5。
继续改
代码语言:javascript复制for (var i = 1; i <= 5; i ) {
(function () {
var j = i
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})()
}
嗯,我们把var j =i 拿到了IIFE里面了,那么这次执行的结果会符合我们的预期吗?答案是:
符合了!!!,那么为什么,我们分析setTimeout所处的作用域中,IIFE每次执行,相当于甩出了一个闭包,每个j都是独立私有的,不在是外面那个i(等同于全局变量)。因此,执行结果符合我们的预期。那每次这样写是不是有点坑。感觉包了好多,又难以理解,有没有更加easy的办法呢?有 ,结合let。
代码语言:javascript复制for (var i = 1; i <= 5; i ) {
let j = i
setTimeout(function timer() {
console.log(j)
}, j * 1000)
}
仅仅只是换了一个let,就做到了我们的想要的预期结果,那么这是为什么呢?
块级作用域,此时的j在每次的循环中存在,下个循环,j就是另外一个j了,换句话说,下次循环,此j非彼j,上述代码实际还等价于
代码语言:javascript复制for (let i = 1; i <= 5; i ) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
其结果是一样,这就是所为什么使用let,少使用var,因为var稍稍使用不慎,就会污染全局。