关于for循环里面异步操作的问题

首先来看一个比较简单的问题,我们想实现的就是每隔1s输出0-4的值,就是这么简单,看下错误写法:

function test() {
    for (var i = 0; i < 5; ++i) {
        setTimeout(function() {
            console.log("index is :", i);
        }, 1000);
    }
}
test();

以上代码会如何输出?输出如下:

index is : 5
index is : 5
index is : 5
index is : 5
index is : 5

而且该操作几乎是在同一时间完成,setTimeout定时根本就没有起作用,这是因为:单线程的js在操作时,对于这种异步操作,会先进行一次“保存”,等到整个for循环执行结束后,此时i的值已经变成5,因为setTimeout是写在for循环中的,相当于存在5次定时调用,这5次调用均是在for循环结束后进行的,所以自然而然输出都是5,正确的实现有几种,一般情况下,我们使用递归实现,如下:

// var i = 0;
// var arr = [0, 1, 2, 3, 4];

// function box6() {
//     if (i < arr.length) {
//         setTimeout(function() {
//             console.log("index is : ", i);
//             i++;
//             box6();
//         }, 1000);
//     }
// }

box6();

function box7(param) {
    if (param < 5) {
        console.log("index is :", param);
        setTimeout(function() {
            box7(param + 1);
        }, 1000)
    }
}
box7(0);

正确实现每隔1s打印输出如下:

index is : 0
index is : 1
index is : 2
index is : 3
index is : 4

使用递归实现的倒计时:

function showTime(count) {
    console.log("count is : ", count);
    if (count == 0) {
        console.log("All is Done!");
    } else {
        count -= 1;
        setTimeout(function() {
            showTime(count);
        }, 1000);
    }
}

showTime(20);

递归调用很好的解决了setTimeout同时执行的情况,如果使用async、await实现,可以如下写法:

var asyncFunc = function(arr, i) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            arr.push(i);
            console.log("index is : ", i);
            resolve();
        }, 1000);
    });
}

var box5 = async function() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        await asyncFunc(arr, i);
    }
    console.log(arr);
}

box5();

同样实现每隔1s正确地打印输出如下:

index is :  0
index is :  1
index is :  2
index is :  3
index is :  4
[ 0, 1, 2, 3, 4 ]

接下来再看个需求:构建一个函数数组,该数组每一项函数的功能是依次输出0-4,错误写法如下:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + list[i];
        result.push(function() { console.log(item + ' ' + list[i]) });
    }
    return result;
}

function testList() {
    var fnlist = buildList([1, 2, 3]);
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

testList();

输出如下:

item3 undefined
item3 undefined
item3 undefined

for循环里面使用匿名函数和直接写setTimeout调用比较类似,但是这里又有点不同,for循环执行结束后,匿名函数开始调用,发现里面存在“item”变量,这时依次会向上级查找,恰好找到循环结束时的item变量值为“list[2]”即为3,item为3但是i的值已经变为3,又因为list[3]的值为undefined,所以这里输出3遍item3 undefined。不信可以修改下数组如下,其余代码不变:

function buildList(list) {
    ...
}

function testList() {
    var fnlist = buildList([6, 7, 8]);
    ...
}

testList();

这里绝对输出的是:

item8 undefined
item8 undefined
item8 undefined

再来看下正确的实现:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + list[i];
        result.push(function(index, it) {
            return function() {
                console.log(it + ' ' + list[index]);
            }
        }(i, item));
    }
    return result;
}

function testList() {
    var fnlist = buildList([6, 7, 8]);
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

testList();

输出如下:

item6 6
item7 7
item8 8

这里主要使用的是即时执行函数,什么是即时执行函数?可以理解为一个封闭的代码块,该代码块中的代码会在定义时立即执行一遍,各个代码块的作用域彼此独立,不会污染外部环境,写法其实有很多种,上面只是一种,同样的还有使用void、+、-、!等等,jquery源码就是直接使用的这里的圆括号写法的这种。

再看几个测试例子:

function box2() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        arr[i] = (function(num) { //自我执行,并传参(将匿名函数形成一个表达式)(传递一个参数)
            return num; //这里的num写什么都可以                    
        })(i); //这时候这个括号里面的i和上面arr[i]的值是一样的都是取自for循环里面的i                            
    }
    return arr;
}
console.log(box2());     //[ 0, 1, 2, 3, 4 ]
function box4() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        arr[i] = (function(num) { //自我执行,并传参(将匿名函数形成一个表达式)(传递一个参数),在这个闭包里面再写一个匿名函数
            return function() {
                return num;
            }
        })(i); //这时候这个括号里面的i和上面arr[i]的值是一样的都是取自for循环里面的i                      
    }
    return arr;
}
console.log(box4());   //[ [Function], [Function], [Function], [Function], [Function] ]

box4这种写法其实跟上面有一种是一致的,就不多说了,其实主要就是闭包,稍微改变一下代码,实现的结果却截然不同,共勉吧。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jimoer

java8在Collection中新增加的方法removeIf

记得我在以前找工作的经历中,遇到过一个面试官问过我一个很基础的问题。问题是:有一个List中有10个元素,我现在想从中删除3个元素,请问怎么做?我当时也没想,就...

3318
来自专栏Java技术

在Java中如何高效判断数组中是否包含某个元素

原文地址:http://www.hollischuang.com/archives/1269

731
来自专栏搞前端的李蚊子

获取Object对象的length

所有JS程序猿(甚至不止JS)都知道,数组(Array)是有length的,通过length属性,可以很方便的获取数组的长度。可以说,只要使用到了数组,就必会使...

34811
来自专栏mathor

导入:什么是数据结构,为什么要学习数据结构,约瑟夫环的数组实现

1105
来自专栏PhpZendo

带你入门 JavaScript ES6 (二)

上一篇学习下一代 JavaScript 语法: ES6 (一),我们学习了关于块作用域变量或常量声明 let 和 const 语法、新的字符串拼接语法模版字面量...

371
来自专栏阮一峰的网络日志

YAML 语言教程

编程免不了要写配置文件,怎么写配置也是一门学问。 YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。 本文介绍 YAML 的语法,...

2606
来自专栏菜鸟前端工程师

JavaScript学习笔记023-对象方法0包装对象0静态属性

602
来自专栏菜鸟前端工程师

JavaScript学习笔记005-运算符

612
来自专栏数据结构与算法

610. 数对的个数

★★   输入文件:dec.in   输出文件:dec.out 简单对比 时间限制:1 s   内存限制:128 MB Description 出题是一件...

2667
来自专栏程序员互动联盟

【C语言系列】C语言概念--基本数据类型简介

1.概述   C 语言包含的数据类型如下图所示: ? 2.各种数据类型介绍 2.1整型   整形包括短整型、整形和长整形。 2.1.1短整形   short ...

3168

扫码关注云+社区