• 关于一道JS面试题的思考


    题目:

    for (var i = 0; i < 5; i++) {
        setTimeout(function() {
            console.log(new Date, i);
        }, 1000);
    }
    console.log(new Date, i);

      1、面对这段代码时给出的结果也不尽相同,以下是典型的答案:

      A. 20% 的人会快速扫描代码,然后给出结果:0,1,2,3,4,5;
      B. 30% 的人会拿着代码逐行看,然后给出结果:5,0,1,2,3,4;
      C. 50% 的人会拿着代码仔细琢磨,然后给出结果:5,5,5,5,5,5;

      只要你对 JS 中同步和异步代码的区别、变量作用域、闭包等概念有正确的理解,就知道正确答案是 C,代码的实际输出是:

      2、如果我们约定,用箭头表示其前后的两次输出之间有 1 秒的时间间隔,而逗号表示其前后的两次输出之间的时间间隔可以忽略,代码实际运行的结果该如何描述?会有下面两种答案:

      A. 60% 的人会描述为:5 -> 5 -> 5 -> 5 -> 5,即每个 5 之间都有 1 秒的时间间隔;
      B. 40% 的人会描述为:5 -> 5,5,5,5,5,即第 1 个 5 直接输出,1 秒之后,输出 5 个 5

      这就要求候选人对 JS 中的定时器的工作机制非常熟悉,循环执行过程中,几乎同时设置了 5 个定时器,一般情况下,这些定时器都会在 1 秒之后触发,而循环完的输出是立即执行的,显而易见,正确的描述是 B。

      3、追问:闭包

      如果这道题仅仅是考察候选人对 JS 异步代码、变量作用域的理解,局限性未免太大,接下来如果期望代码的输出变成:5 -> 0,1,2,3,4,该怎么改造代码?熟悉闭包的同学很快能给出下面的解决办法:

    for (var i = 0; i < 5; i++) {
        (function(j){
            setTimeout(function() {
                console.log(new Date, j);
            }, 1000);
        })(i);
    }
    console.log(new Date, i);

      巧妙的利用 IIFE(Immediately Invoked Function Expression:声明即执行的函数表达式)来解决闭包造成的问题,确实是不错的思路,但是初学者可能并不觉得这样的代码很好懂,至少笔者初入门的时候这里琢磨了一会儿才真正理解。

      有没有更符合直觉的做法?答案是有,我们只需要对循环体稍做手脚,让负责输出的那段代码能拿到每次循环的 i 值即可。该怎么做呢?利用 JS 中基本类型(Primitive Type)的参数传递是按值传递(Pass by Value)的特征,不难改造出下面的代码:

    var output = function(i){
        setTimeout(function() {
            console.log(new Date, i);
        }, 1000);
    }
    for (var i = 0; i < 5; i++) {
        output(i);//值传递传个i值过去
    }
    console.log(new Date, i);

      4、追问

      如果期望代码的输出变成 0 -> 1 -> 2 -> 3 -> 4 -> 5,并且要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5(这里使用大概,是为了避免钻牛角尖的同学陷进去,因为 JS 中的定时器触发时机有可能是不确定的)

    for (var i = 0; i < 5; i++) {
         (function(j) {
              setTimeout(function() {
               console.log(new Date, j);
              }, 1000 * j); // 这里修改 0~4 的定时器时间
         })(i);
    }
     
    setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
     console.log(new Date, i);
    }, 1000 * i);

      不得不承认,这种做法虽粗暴有效,但是不算是能额外加分的方案。如果把这次的需求抽象为:在系列异步操作完成(每次循环都产生了 1 个异步操作)之后,再做其他的事情,代码该怎么组织?聪明的你是不是想起了什么?对,就是 Promise

      ES6、ES7的解决方案(后续)。

  • 相关阅读:
    redis的安装使用
    重大需求分析开发第一天
    学习进度条-第六周
    学习进度条-第五周
    软件工程个人课程总结
    学习进度条-第十六周
    团队冲刺第二阶段第十天
    团队冲刺第二阶段第十一天
    响当当队-Beta版总结会议
    学习进度条-第十五周
  • 原文地址:https://www.cnblogs.com/goloving/p/7203499.html
Copyright © 2020-2023  润新知