Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
本题难度:⭐ ⭐
执行下面这段代码,输出的结果是什么?
const arr = []
for (var i = 0; i < 5; i++) {
arr.push(function () {
console.log(i)
})
}
arr[0]()
arr[1]()
arr[2]()
你可能会脱口而出,这么简单,输出 0,1,2。
但很遗憾,事实并非如此,最终输出结果为 5,5,5,如下图:
原因是在 for 循环里定义变量 i,其实就相当于在全局定义了一个变量 i。
for (var i = 0; i < 5; i++) {
// ...
}
// 这两种写法完全等价
var i = 0
for (; i < 5; i++) {
// ...
}
程序运行时,for 循环一瞬间就结束了,在打印执行之前,i 就已经变成 5 了,最后执行时输出的都是 5。
const arr = []
var i = 0
for (; i < 5; i++) {
arr.push(function () {
console.log(i)
})
}
console.log('i :>> ', i) // 输出5,在打印执行前,输出 i,就已经是 5 了
console.log('window.i :>> ', window.i); // 输出5,访问 i 其实就相当于访问 window.i,也是5
arr[0]() // 5
arr[1]() // 5
arr[2]() // 5
那么,如何按顺序打印出 0,1,2,3,4 呢?
其实只需要把每次执行 for 循环的 i 存起来,就能解决全部输出 5 的问题了。
有三种思路来解决循环陷阱:
- 使用闭包存值。
- 使用块级作用域存值。
- 封装一个函数,把每次循环的值传递过去
使用闭包解决循环陷阱问题
还记得闭包怎么实现的吗,在函数内部定义私有变量,并想办法在外部访问这个私有变量,就可以这么写:
const arr = []
for (var i = 0; i < 5; i++) {
const fn = function (n) { // 定义闭包函数
arr.push(function () {
console.log(n)
})
}
fn(i) // 执行函数,把每次执行 for 循环的 i 传进去
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
这样就可以把每次执行 for 循环的 i 存在闭包里
也可以用立即执行函数来写,就可以直接写匿名函数,少写点代码:
const arr = []
for (var i = 0; i < 5; i++) {
(function (n) { // 立即执行函数
arr.push(function () {
console.log(n)
})
})(i) // 把每次执行 for 循环的 i 传进去
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
效果是一模一样的
拓展:解决循环陷阱的其他方式
使用块级作用域存值
其实还有一种更简单的写法,可以实现按顺序输出 0,1,2,3,4,就是使用 let 来定义变量 i,把 i 的值存进块级作用域里,代码如下:
const arr = []
for (let i = 0; i < 5; i++) {
arr.push(function () {
console.log(i)
})
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
函数传参解决
其实还有一种方法可以解决这个问题,而且比上面的解决方案都符合直觉,
const arr = []
for (var i = 0; i < 5; i++) {
print(i)
}
function print(i) {
arr.push(function () {
console.log(i)
})
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
利用的原理就是原始数据类型作为参数传入函数时,是按值传递的,就可以把每次的 i 值准确地传到 print 函数里。
使用 bind 函数
上面的例子就是封装了一个 print 函数传参来解决,其实也可以不用专门封装,可以用 bind 函数来解决。
bind 函数可以显式改变 this 指向,会返回一个新的函数,我们利用 bind 函数会返回一个新函数的特性,来解决循环陷阱的问题。
const arr = []
for (var i = 0; i < 5; i++) {
arr.push(function (n) {
console.log(n)
}.bind(this, i)) // 不用改变 this 指向,我们主要是为了返回一个新的函数
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
结尾
关于闭包有不理解的,可以看我的这篇文章:
如果我的文章对你有帮助,你的👍就是对我的最大支持^_^
你也可以关注《前端每日一问》这个专栏,防止失联哦~
我是阿林,输出洞见技术,再会!
上一篇:
下一篇;