众所周知 var 在循环里有坑
本文介绍基于经典例子——打印 0 到 5
关于打印 0 到 5 这个任务,该怎么办呢?很自然地想到使用循环打印
但如果是普通的循环打印,就不会有这篇文章了
我们要做的是——通过 setTimeout 的循环打印
首先是 c 风格的循环打印代码
1 | let i = 0 |
run 了一看,ん??怎么打了 6 个 6
原因是此时 i 变量相对于 for 来说处在全局,而不是封闭的词法环境
光是这个原因,都足以抹平 let 和 var 的差距了
再加上 setTimeout 的宏任务,会在下一个事件循环开启的时候才执行(关于宏任务,会在其它文章介绍,本篇略
于是执行就变成了
- 声明 i=0
- 循环设置定时器,定时器中索引指向 i
- 循环完毕,i=6
- 当前事件循环清空,开启下一个事件循环
- 通过索引发现 i=6,于是所有定时器都打印了 6
那怎么办呢?此时注意到,刚才我提到了一个关键词——封闭的词法环境
那采用闭包的思想,让 i 处于封闭的词法环境,不就可以了!
来试试
1 | for(let i = 0; i<6; i++){ |
现在就可以正常打印 0 到 5 了!
为什么呢?因为此时 i 的作用域仅限于 for 之内,每个 setTimeout 获取 i 的索引的时候,获取到的都是不一样的值,所以现在可以正常打印
但是在这里,换成 var 就大不相同了
因为通过 var 声明的变量实际上会绑定到 window,所以此时获取的索引还是相同,还是要打印 6 个 6
那么,还有什么别的,能通过 setTimeout 打印 0 到 5 的办法呢?
容易想到,有两种改变
- 通过在 setTimeout 中自增来实现
- 通过 generator 实现
先说第一种,代码很简单,把控制自增的部分从 for 移动到 setTimeout 就可以了
1 | let n = 0 |
注意到此时 let i 除了控制循环,已经和 setTimeout 没有关系了
配合后置 ++ 运算符的先取值再自增特性,很容易实现了在 setTimeout 中的自增
那么 generator 怎么实现呢?
我不想在这里介绍 generator 语法((实在太长了
直接给出例子
1 | function* generateSequence(start, end) { |
我们可以声明一个生成器工厂,每当运行该工厂方法的时候,传入上下界,然后返回一个生成器
每当生成器调用 next 方法的时候,都会返回一个对象,该对象包含 value 字段和 done 字段,分别表示抛出的值和该生成器当前的状态
显然生成器的调用是不可逆的,所以也约等于刚才说的在 setTimeout 中自增
以上
你还有别的办法吗?可以通过本站的”联系方式”向我发邮件~~