for(var i = 0; i < 5; i++){
setTimeout(function() {
console.log(i)
},50)
}
console.log('哦吼~')
// '哦吼~'
//5
//5
//5
//5
//5
为什么打印结果是先打印哦吼,然后再是5个5呢?
首先,js是单线程的,也就是说一次只执行一个事件. 但是又分为主线程和事件队列, 对于回调函数这种异步的操作,会被加入到事件队列,等待主线程空闲的时候来执行事件队列里的事件
计时器并不是每隔一段时间执行处理函数,而是每隔一定时间将处理函数放入事件队列,等待主线程空闲的时候来执行
那么上述代码可以解析为:
for (var i = 0; i < 5; i++) {
}
console.log('哦吼~'); // 主线程结束
// 进入事件队列执行, 此时的 i 已经是5了
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
setTimeout(function () {
console.log(i)
})
那么我们怎么让 for 循环中的 i 可以按顺序输出呢?
方法一: 将 for 循环放入计时器内部
setTimeout(()=>{
for(var i = 0; i < 5; i++) {
console.log(i)
}
},50)
console.log('哦吼~')
// '哦吼~'
//0
//1
//2
//3
//4
这个就不解释了吧
方法二: 将计时器放在自执行函数里
for (var i = 0; i < 5; i++) { // var 存在声明提升,所以每次 循环执行的是赋值语句, 不会重复声明,
实际上 i 只被声明了一次
((i) => {
setTimeout(() => console.log(i))
})(i)
}
console.log('哦吼~')
// '哦吼~'
//0
//1
//2
//3
//4
( 用箭头函数写的,看着很恶心,希望以后我自看的时候看得懂 )
自执行函数会形成一个作用域,而计时器形成了一个闭包,可以访问自执行函数保存下来的变量 i,即使自执行函数被调用了,他的作用域还在,闭包中仍然可以访问到 i,就相当于用自执行函数保存下来了不同的i
方法三: ES6中的 let 关键字
for(let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
},50)
}
console.log('哦吼~')
// '哦吼~'
//0
//1
//2
//3
//4
1. 由于 var 命令的变量提升机制, var 命令实际上只会执行一次
2. 而 let 命令不存在变量提升, 所以每次循环都会执行一次, 声明一个新变量( 但初始化的值不一样 ).
3. for 的每次循环都是不同的块级作用域, let 声明的变量是块级作用域的, 所以不存在重复声明的问题.
4. let 声明变量的 for 循环里, 每个匿名函数实际上引用的都是一个新的变量.
{
let i = 0
if(i<5){
setTimeout(() => console.log(i), 50)
num = i
}
}
{ num++
let i = num
if(i<5){
setTimeout(() => console.log(i), 50)
}
}
{ num++
let i = num
if(i<5){
setTimeout(() => console.log(i), 50)
}
}
{ num++
let i = num
if(i<5){
setTimeout(() => console.log(i), 50)
}
}
{ num++
let i = num
if(i<5){
setTimeout(() => console.log(i), 50)
}
}
上面的代码仅仅是为了方便理解,let 关键字形成的块级作用域产生了类似闭包的效果, 并且在 for 循环中使用 let 来定义循环遍历还会有一个特殊效果: 每一次循环都会重新声明变量 i, 随后的每个循环都会使用上一个循环结束时的值(完成了 +1 之后的值)来初始化这个变量
方法四: try catch 语句
for (var i = 0; i < 5; i++) {
try {
throw (i)
} catch (j) {
setTimeout(function () {
console.log(j);
});
}
}
console.log('哦吼~')
// '哦吼~'
//0
//1
//2
//3
//4
catch块中会指定一个标识符( 上面例子中的 j ), 这个标识符用来保存 throw 出来的值( 也就是 i ). catch 块会形成一个会计作用域, 所以每次循环抛出来的 i 都被保存到了不同的作用域里面, 类似于闭包
总结参考了很多前辈的文章,希望对你有用~