zoukankan      html  css  js  c++  java
  • 一道经典面试题-----setTimeout(function(){},0)和引发发的其它面试题

    先看题:

     for (var i = 0; i < 3; i++) {
         setTimeout(function() {
             console.log(i);
         }, 0);
         console.log(i);
     }

    结果是:0 1 2 3 3 3
    很多公司面试都爱出这道题,此题考察的知识点还是蛮多的。 都考察了那些知识点呢?
    异步、作用域、闭包。
    我们来简化此题:

     setTimeout(function() {
         console.log(1);
     }, 0);
     console.log(2);   //先打印2,再打印1

    因为是setTimeout是异步的。
    正确的理解setTimeout的方式(注册事件): 有两个参数,第一个参数是函数,第二参数是时间值。 调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。

    就像我们给按钮绑定事件一样:

     btn.onclick = function() {
       alert(1);
     };

    这么写完,会弹出1吗。不会!!只是绑定事件而已! 必须等我们去触发事件,比如去点击这个按钮,才会弹出1。

    setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。

    setTimeout的时间值是怎么回事呢?

     setTimeout(fn, 2000)

    程序会不会报错? 不会!而且还会准确得打印1。为什么? 因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!

    所以我们进行dom操作。可以先绑定事件,然后再去写其他逻辑。

     window.onload = function() {
             fn();
     }
     var fn = function() {
             alert('hello')
     };

    这么写,完全是可以的。因为异步!

    es5中是没有块级作用域的。

    for (var i = 0; i < 3; i++) {}
    console.log(i); //3,也就说i可以在for循环体外访问到。所以是没有块级作用域。

    这回我们再来看看原题。
    原题使用了for循环。循环的本质是干嘛的? 是为了方便我们程序员,少写重复代码。

    原题等价于:

      var i = 0;
      setTimeout(function() {
          console.log(i);
      }, 0);
      console.log(i);
      i++;
      setTimeout(function() {
          console.log(i);
      }, 0);
      console.log(i);
      i++;
      setTimeout(function() {
          console.log(i);
      }, 0);
      console.log(i);
      i++;

    因为setTimeout是注册事件。根据前面的讨论,可以都放在后面。
    原题又等价于如下的写法:

       var i = 0;
       console.log(i);
       i++;
       console.log(i);
       i++;
       console.log(i);
       i++;
       setTimeout(function() {
           console.log(i);
       }, 0);
       setTimeout(function() {
          console.log(i);
       }, 0);
       setTimeout(function() {
           console.log(i);
       }, 0);  //弹出 0 1 2 3 3 3

    怎么保证能弹出0,1, 2呢?

    for (var i = 0; i < 3; i++) {
          setTimeout((function(i) {
              return function() {
                  console.log(i);
              };
          })(i), 0);  //改为立即执行的函数
          console.log(i);  
      }

    就可以打印出来了。

    接下来聊聊

    引发的面试题

    for (var i = 0; i < 5; i++) {
        console.log(i);
        setTimeout(function () {
            console.log(i)    //5个10
            var timestamp = Date.parse(new Date());
            console.log(timestamp);
        }, i*1000);
    }

    结果打印:0,1,2,3,4,5,5,5,5,5
    好奇的是为什么不是5s后一次打印或则每5s打印一次
    查了一些资料看得比较明白的是下面的理解:
    setTimeout是一次执行函数,这里是i*1s后执行,仅仅执行一次;for(var i=0;i<5;i++),i的每次取值都是执行setTimeout这个函数,并没有执行setTimeout里面的function(即闭包函数),
    setTimeout里面的function是有setTimeout的定时触动的,也就是i*1s后执行,也就是说i从0~5时,一共执行了5次的setTimeout()函数,此时的i的值是5,
    由于for语句的执行速度远小于1s,所以,1s后,由setTimeout()函数定时触动的闭包函数function()开始执行,alert(i);i的值已经是5了,所以相继打印5次5
    外加老姚的回答:
    setTimeout(fn, time),首先这是个函数setTimeout执行,他是同步执行的。表示的意思是告诉浏览器,time时间后,要执行fn,只是“告诉”而已。
    time时间后,浏览器把fn插入事件队列中,如果插入时,事件队列时空的,那么fn就立即执行了。因为fn在setTimeout执行完毕之后才执行的,因此是异步的,即异步回调。
    而setTimeout(fn, 1000 * i)可以写成
    var j = 1000 * i;
    setTimeout(fn, j)
    因此是同步执行的。
    fn中的i,是闭包中的i,始终拿到i最新的保存值的,而轮到fn执行时,i已经累加成了5了。

  • 相关阅读:
    Oracle修改表Table所属表空间及Clob、Blob字段的处理
    MyBatis返回多表连接结果
    MyBatis查询结果resultType返回值类型详细介绍
    SpringBoot之分页PageHelper
    Postman简单用法以及转cURL等命令的正确姿势
    postman 巧用cURL
    Spring Boot设置跨域访问
    springboot设置cors跨域请求的两种方式
    @Configuration使用
    @GetMapping和@PostMapping接收参数的格式
  • 原文地址:https://www.cnblogs.com/wangking/p/8405983.html
Copyright © 2011-2022 走看看