前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >js闭包就那么回事

js闭包就那么回事

原创
作者头像
brzhang
修改2020-05-04 12:33:04
6590
修改2020-05-04 12:33:04
举报
文章被收录于专栏:玩转全栈

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档