前言
大家好,for
循环应该是老生常谈的问题了,但我今天还是想再回顾一下,探索其中原理的同时,也能学到一些有用的知识。下文将通过局部变量、块级作用域、闭包
三种方式来展示哪种方法可以解决for
循环存在的缺陷,并且说明为什么 不能解决以及为什么 能解决,并且会演示在浏览器
中如何进行打断点
及如何查看
代码运行生成的局部变量、块级作用域以及闭包
。
关键词: for循环、局部变量、块级作用域、闭包
for循环探索
for循环使用var声明变量的缺陷
js
for(var i = 0; i <= 2; i++) {
setTimeout(() => {
console.log(i) // 打印三次3
}, 1000 * i)
}
执行后,会每秒打印一个3
,这是因为在延时器回调函数执行前,for
循环已经结束,这时i=3
。
而延时器的回调函数在没有执行前,并不知道i
的具体值是什么,始终保留的是对i
的引用。
当回调函数执行时,去读取i
的值,这是发现i
已经是3
了,所以打印结果为三次3
。
创建局部变量探索
js
for(var i = 0; i <= 2; i++) {
var cb = () => {
const j = i // 每次循环保存i的值
console.log(j) // 打印三次3
}
setTimeout(cb, 1000 * i)
}
我们在浏览器的sources
标签下打开运行的代码文件,在22
行处打了个断点,运行后可以看到右边Scope
的Local
下可看到局部变量j
始终是3
,由于没有形成Closure(闭包)
,所以回调函数里给j
赋值的i
始终是引用回调函数执行时i
的具体值,也就是循环结束后i
的值,所以并没有真正解决问题。
每次循环重新var声明变量
js
for(var i = 0; i <= 2; i++) {
var j = i
setTimeout(() => {
console.log(j) // 打印三次2
}, 1000 * j)
}
console.log(j) // 2
for
循环处声明的var
变量相对于声明在全局作用域window.j
(确切来说是在for
循环外的作用域),每次循环只是简单的赋值,并不用解决问题。
使用let创建块作用域
js
for(var i = 0; i < 3; i++) {
// let隐式声明块级作用域
let j = i
setTimeout(() => {
console.log(j) // 0,1,2
}, 1000 * i)
// 等价于let显示声明块级作用域
// {
// let j = i
// setTimeout(() => {
// console.log(j) // 0,1,2
// }, 1000 * i)
// }
}
在Scope
下可以看到形成了一个块级作用域,下面保存着j
变量。每次执行会分别打印0,1,2,
所以通过块级作用域可以解决for
循环的缺陷。
使用let创建块作用域(简写版)
js
for(let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i) // 0,1,2
}, 1000 * i)
}
使用IIFE(立即执行函数)创建闭包
js
for(var i = 0; i < 3; i++) {
(function(j){
console.log(i) // 0,1,2
})(i)
}
相当于每次执行循环都会创建一个闭包 ,j
保留着对当次循环的i
的具体值的引用。
在Scope(作用域)
下的Closure(闭包)
处可看到j形成了闭包。
所以通过闭包也能解决问题。
总结
解决for循环缺陷的方法可以使用let块级作用域 或者闭包 的方式。注意 区分局部作用域
和块级作用域
是不同的概念,局部变量并不能真正解决问题。