今天了解了一下js闭包这块的内容,还是有点诡异的,将实践结果记录一下,看完只后,我敢说,闭包就那么回事,所谓的闭包,其实就是客户端开发中,其实就是叫做内存泄漏,就是不当引用导致对象没法得到释放,哈哈,玩笑开得有点过了,只是有点像哈,其实并不全是。
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控制台上去执行的时候,实际上基本上等价于。
console.log(eval(`let ret = 0 let fun = function () { return ret++ } for (var i = 1; i <= 5; i++) { fun() }`))而,我们知道,eval返回值的规则,如果你不知道,可以在这里了解eval() - JavaScript | MDN
因为,回到我们的最初代码
for(var i=1;i<=5;i++){ setTimeout(function timer(){ console.log(i) },i*1000) }这里打印了一个8903就不难理解了。但是,我想说的是,这个说了这么多,只是一个插曲而已,本文的重点不是这个,就上述这段代码,我们的本意是想让它打印1.2.3.4.5的,结果你给我5个6。那么,改为下面这个方式呢?
for (var i = 1; i <= 5; i++) { (function () { setTimeout(function timer() { console.log(i) }, i * 1000) })() }使用一个IIFE将其包裹起来,那么实际的执行结果将会符合我们的预期吗?下不要看下面的答案,同样,你自己感受一下。
没错,同样的道理,并不符合我们的预期。
继续改,看下面
for (var i = 1; i <= 5; i++) { var j = i; (function () { setTimeout(function timer() { console.log(j) }, j * 1000) })() }可以看到,效果确实和上一次差不多,但是为什么输出的是5个5呢?那是因为,最后一次i++影响不到j了,所以是5。
继续改
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。
for (var i = 1; i <= 5; i++) { let j = i setTimeout(function timer() { console.log(j) }, j * 1000) }仅仅只是换了一个let,就做到了我们的想要的预期结果,那么这是为什么呢?
块级作用域,此时的j在每次的循环中存在,下个循环,j就是另外一个j了,换句话说,下次循环,此j非彼j,上述代码实际还等价于
for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }其结果是一样,这就是所为什么使用let,少使用var,因为var稍稍使用不慎,就会污染全局。
---来自腾讯云社区的---brzhang
微信扫一扫打赏
支付宝扫一扫打赏