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


    https://www.cnblogs.com/vipzhou/p/6519552.html

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

     

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

    1
    2
    3
    4
    5
    6
    7
    8
    function test() {
        for (var i = 0; i < 5; ++i) {
            setTimeout(function() {
                console.log("index is :", i);
            }, 1000);
        }
    }
    test();

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

    1
    2
    3
    4
    5
    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,正确的实现有几种,一般情况下,我们使用递归实现,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 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打印输出如下:

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

    使用递归实现的倒计时:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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实现,可以如下写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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正确地打印输出如下:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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();

    输出如下:

    1
    2
    3
    item3 undefined
    item3 undefined
    item3 undefined

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function buildList(list) {
        ...
    }
     
    function testList() {
        var fnlist = buildList([6, 7, 8]);
        ...
    }
     
    testList();

    这里绝对输出的是:

    1
    2
    3
    item8 undefined
    item8 undefined
    item8 undefined

    再来看下正确的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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();

    输出如下:

    1
    2
    3
    item6 6
    item7 7
    item8 8

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

    再看几个测试例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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 ]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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这种写法其实跟上面有一种是一致的,就不多说了,其实主要就是闭包,稍微改变一下代码,实现的结果却截然不同,共勉吧。。。

  • 相关阅读:
    【Java高级工程师蜕变之路】008 Spring核心思想
    【Java高级工程师蜕变之路】003 MyBatis高级进阶
    【Java高级工程师蜕变之路】007 Spring基础回顾
    【Java高级工程师蜕变之路】006 ErrorContext的机制分析
    【Java高级工程师蜕变之路】002 自定义持久层框架
    【Java高级工程师蜕变之路】001 JDBC的问题分析
    如何配置mac环境下的JAVA_HOME
    【Java高级工程师蜕变之路】005 MyBatis插件及其应用
    【Java高级工程师蜕变之路】004 MyBatis源码剖析
    ConcurrentHashMap中的2的n次方幂上舍入方法
  • 原文地址:https://www.cnblogs.com/chaoyuehedy/p/10658804.html
Copyright © 2020-2023  润新知