循环中的异步&&循环中的闭包

53次阅读

共计 3079 个字符,预计需要花费 8 分钟才能阅读完成。

原文链接在这之前先要了解一下
for 循环中 let 和 var 的区别
var 是函数级作用域或者全局作用域,let 是块级作用域看一个例子
function foo() {
for (var index = 0; index < array.length; index++) {
//.. 循环中的逻辑代码
}
console.log(index);//=>5
}
foo()
console.log(index)//Uncaught ReferenceError: index is not defined
foo 函数下的 index 输出 5,全局下的 index 不存在现在我们把 var 换为 let
function foo() {
for (let index = 0; index < array.length; index++) {
//.. 循环中的逻辑代码
}
console.log(index)//Uncaught ReferenceError: index is not defined
}
foo()
报错了,index 不在 foo 函数作用域下,当然肯定也不会再全局下因为 var 和 let 的这个区别(当然 var 和 let 的区别不止于此)所以导致了下面的这个问题关于 var 的
const array = [1, 2, 3, 4, 5]
function foo() {
for (var index = 0; index < array.length; index++) {
setTimeout(() => {
console.log(index);
}, 1000);
}
}
foo()
看下输出关于 let 的
const array = [1, 2, 3, 4, 5]
function foo() {
for (let index = 0; index < array.length; index++) {
setTimeout(() => {
console.log(index);
}, 1000);
}
}
foo()
看下输出因为 var 和 let 在作用域上的差别,所以到这了上面的问题使用 var 定义变量的时候,作用域是在 foo 函数下,在 for 循环外部,在整个循环中是全局的,每一次的循环实际上是为 index 赋值,循环一次赋值一次,5 次循环完成,index 最后的结果赋值就为 5;就是被最终赋值的 index,就是 5;let 的作用局的块级作用局,index 的作用域在 for 循环内部,即每次循环的 index 的作用域就是本次循环,下一次循环重新定义变量 index;所以 index 每次循环的输出都不同这里还有另外一个问题,setTimeout,这是一个异步,这就是我们今天要讨论的
循环中的异步
setTimeout(func,time) 函数运行机制
setTimeout(func,time) 是在 time(毫秒单位)时间后执行 func 函数。浏览器引擎按顺序执行程序,遇到 setTimeout 会将 func 函数放到执行队列中,等到主程序执行完毕之后,才开始从执行队列(队列中可能有多个待执行的 func 函数)中按照 time 延时时间的先后顺序取出来 func 并执行。即使 time=0, 也会等主程序运行完之后,才会执行。
一个需求,一个数组 array[1,2,3,4,5], 循环打印,间隔 1 秒
上面的 let 是循环打印了 12345,但是不是间隔 1s 打印的,是在 foo 函数执行 1s 后,同时打印的
方式一 放弃 for 循环,使用 setInterval
function foo(){
let index = 0;
const array = [1, 2, 3, 4, 5]

const t = setInterval(()=>{
if (index < array.length) {
console.log(array[index]);
}
index++;
}, 1000);

if (index >= array.length) {
clearInterval(t);
}
}
foo()
我们上面说到,当 for 循环遇到了 var,变量 index 的作用域在 foo 函数下,循环一次赋值一次,5 次循环完成,index 最后的结果赋值就为 5;就是被最终赋值的 index,就是 5;
方式二,引入全局变量
代码执行顺序是,先同步执行 for 循环,再执行异步队列,在 for 循环执行完毕后,异步队列开始执行之前,index 经过 for 循环的处理,变成了 5。所以我们引入一个全局变量 j,使 j 在 for 循环执行完毕后,异步队列开始执行之前,依然是 0,在异步执行时进行累加
var j = 0;
for (var index = 0; index < array.length; index++) {
setTimeout(() => {
console.log(j);
j++;
}, 1000 * index)
}
方式三 for 循环配合 setTimeout(常规思路,不赘述,面试必备技能)
const array = [1, 2, 3, 4, 5]
function foo() {
for (let index = 0; index < array.length; index++) {
setTimeout(() => {
console.log(index);
}, 1000*index);
}
}
foo()
方式四,通过闭包实现
开始讨论方式四之前我推荐先阅读一遍我之前写过一篇文章谈一谈 javascript 作用域我们对上面的问题再次分析,for 循环同步执行,在 for 循环内部遇到了 setTimeout,setTimeout 是异步执行的,所以加入了异步队列,当同步的 for 循环执行完毕后,再去执行异步队列,setTimeout 中有唯一的一个参数数 index 方式三可行,是因为 let 是块级作用域,每次 for 执行都会创建新的变量 index,for 循环执行完毕后,异步执行之前,创建了 5 个独立的作用域,5 个 index 变量,分别是 0,1,2,3,4,相互独立,互不影响,输出了预期的结果如果说每次循环都会生成一个独立的作用域用来保存 index,问题就会得到解决,所以,我们通过闭包来实现
const array = [1, 2, 3, 4, 5]

function foo() {
for (var index = 0; index < array.length; index++) {
function fun(j) {
setTimeout(function () {
console.log(j);
}, 1000 * j);
}
fun(index)
}
}
foo()
setTimeout 中的匿名回调函数中引用了函数 fun 中的局部变量 j,所以当 fun 执行完毕后,变量 j 不会被释放,这就形成了闭包当然我们可以对此进行一下优化
const array = [1, 2, 3, 4, 5]

function foo() {
for (var index = 0; index < array.length; index++) {
(function(j) {
setTimeout(function () {
console.log(j);
}, 1000 * j);
})(index)
}
}
foo()
将 foo 函数改为匿名的立即执行函数,结果是相同的
总结
for 循环本身是同步执行的,当在 for 循环中遇到了异步逻辑,异步就会进入异步队列,当 for 循环执行结束后,才会执行异步队列当异步函数依赖于 for 循环中的索引时(一定是存在依赖关系的,不然不会再循环中调动异步函数)要考虑作用域的问题,在 ES6 中使用 let 是最佳的选择,当使用 var 时,可以考虑再引入一个索引来替代 for 循环中的索引,新的索引逻辑要在异步中处理也可以使用闭包,模拟实现 let 在实际开发过程中,循环调用异步函数,比 demo 要复杂,可能还会出现 if 和 else 判断等逻辑,具体的我们下次再续
参考通过 for 循环每隔两秒按顺序打印出 arr 中的数字 setTimeOut 和闭包《你不知道了 JavaScript》上卷

正文完
 0