js闭包就那么回事

2020-05-04 12:33:04 浏览数 (2)

今天了解了一下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稍稍使用不慎,就会污染全局。

0 人点赞