JS循环打印与延迟执行

众所周知 var 在循环里有坑

本文介绍基于经典例子——打印 0 到 5

关于打印 0 到 5 这个任务,该怎么办呢?很自然地想到使用循环打印

但如果是普通的循环打印,就不会有这篇文章了

我们要做的是——通过 setTimeout 的循环打印

首先是 c 风格的循环打印代码

1
2
3
4
5
6
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}

run 了一看,ん??怎么打了 6 个 6

原因是此时 i 变量相对于 for 来说处在全局,而不是封闭的词法环境

光是这个原因,都足以抹平 let 和 var 的差距了

再加上 setTimeout 的宏任务,会在下一个事件循环开启的时候才执行(关于宏任务,会在其它文章介绍,本篇略

于是执行就变成了

  1. 声明 i=0
  2. 循环设置定时器,定时器中索引指向 i
  3. 循环完毕,i=6
  4. 当前事件循环清空,开启下一个事件循环
  5. 通过索引发现 i=6,于是所有定时器都打印了 6

那怎么办呢?此时注意到,刚才我提到了一个关键词——封闭的词法环境

那采用闭包的思想,让 i 处于封闭的词法环境,不就可以了!

来试试

1
2
3
4
5
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}

现在就可以正常打印 0 到 5 了!

为什么呢?因为此时 i 的作用域仅限于 for 之内,每个 setTimeout 获取 i 的索引的时候,获取到的都是不一样的值,所以现在可以正常打印

但是在这里,换成 var 就大不相同了

因为通过 var 声明的变量实际上会绑定到 window,所以此时获取的索引还是相同,还是要打印 6 个 6

那么,还有什么别的,能通过 setTimeout 打印 0 到 5 的办法呢?

容易想到,有两种改变

  1. 通过在 setTimeout 中自增来实现
  2. 通过 generator 实现

先说第一种,代码很简单,把控制自增的部分从 for 移动到 setTimeout 就可以了

1
2
3
4
5
6
let n = 0
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(n++)
},0)
}

注意到此时 let i 除了控制循环,已经和 setTimeout 没有关系了

配合后置 ++ 运算符的先取值再自增特性,很容易实现了在 setTimeout 中的自增

那么 generator 怎么实现呢?

我不想在这里介绍 generator 语法((实在太长了

直接给出例子

1
2
3
4
5
6
7
8
9
10
11
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}

let g=generateSequence(0,5)

for(var i = 0; i<6; i++){
setTimeout(()=>{
console.log(g.next().value)
},0)
}

我们可以声明一个生成器工厂,每当运行该工厂方法的时候,传入上下界,然后返回一个生成器

每当生成器调用 next 方法的时候,都会返回一个对象,该对象包含 value 字段和 done 字段,分别表示抛出的值和该生成器当前的状态

显然生成器的调用是不可逆的,所以也约等于刚才说的在 setTimeout 中自增

以上

你还有别的办法吗?可以通过本站的”联系方式”向我发邮件~~

--It's the end.Thanks for your read.--