从setTimeout看js函数执行

  老实说,写这篇文章的时候内心是有点压抑的,由于受到打击了,为何?就 由于喜欢折腾不当心看到了这个"简单"的函数:javascript

        for (var i = 0; i < 5; i++) {
            setTimeout(function () {
                console.log(i)
            }, i * 1000);
        }
        console.log(i);
  什么?这不就是我好久以前看到的先打印一个5,再打印一个5,以后每隔一秒就打印一个5,直到打印完6个5的实现方法吗?那么问题来了,若是我要依次打印0,1,2,3,4,5的话我该怎么办,其实在这以前我就知道有这两个方法:一个是这样:
   function log(i){
   setTimeout(function(){
    console.log(i)
    },i*1000)
   };
  
   for (var i = 0; i < 5; i++) {
            log(i) ;
        }
        console.log(i);
   还有一个是这样:
   for(var i=0;i<5;i++){
    (function(e){
      setTimeout(function(){
       console.log(e)
      },i*1000);
    })(i);
   };
  console.log(i);
  不怕笑话,在这以前我是没搞懂这两个函数真正意义上的做用是用来干吗的,只强迫本身这样记住这样修改就能够了,可是如今不行啊,我有强迫症啊!因而,我慢慢分析了一下,发现上面那段代码能够分离成这样:
  i=0时;知足条件;
  setTimeout(function(){
    console.log(i)
    },0*1000);
  
  i=1时;知足条件;
  setTimeout(function(){
    console.log(i)
    },1*1000);
  
i=2时;知足条件;
  setTimeout(function(){
    console.log(i)
    },2*1000);
  
i=3时;知足条件;
  setTimeout(function(){
    console.log(i)
    },3*1000);
i=4时;知足条件;
  setTimeout(function(){
    console.log(i)
    },4*1000);
i=5时,不知足条件,跳出循环,接着执行for循环后面的console.log(i),打印5;最后依次每秒打印5;
  真有意思,为何setTimeout里面的console.log会是后于for循环外面的console.log执行呢?直到我认识到了这个单词=>"队列", 队列又有宏任务队列(Macro Task)以及微任务队列(Micro Task)之分,在javascript中:
  1. macro-task包括:script(总体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。java

  2. micro-task包括:process.nextTick, Promises, Object.observe, MutationObserverpromise

  3. 上面函数的setTimeout就属于宏任务

在js中,事件循环的顺序是从script开始第一次循环,随后全局上下文进入函数调用栈,碰到macro-task就将其交给处理它的模块处理完以后将回调函数放进macro-task的队列之中,碰到micro-task也是将其回调函数放进micro-task的队列之中。直到函数调用栈清空只剩全局执行上下文,而后开始执行全部的micro-task。当全部可执行的micro-task执行完毕以后。循环再次执行macro-task中的一个任务队列,执行完以后再执行全部的micro-task,就这样一直循环。异步

 这就是为何setTimeout里面的console.log会是后于for循环外面的console.log执行,在函数执行上下文中,seiTimeout函数会被放处处理他的macro-task的队列之中,因此循环的时候setTimeout里面的function是不会被执行的,而是等到全部总体代码(非队列)跑完以后才会执行队列中的函数;写到这里,可能会有点懵逼,其实我也有点懵逼,哈哈哈!!函数

  为了加深理解,还能够试试在里面加入Promise,因而就有了这个:spa

(function copy() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) {  i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
解释一下=>
1.首先,script任务源先执行,全局上下文入栈。
2.script任务源的代码在执行时遇到setTimeout,做为一个macro-task,将其回调函数放入本身的队列之中。
3.script任务源的代码在执行时遇到Promise实例。Promise构造函数中的第一个参数是在当前任务直接执行不会被放入队列之中,所以此时输出 1 。
4.在for循环里面遇到resolve函数,函数入栈执行以后出栈,此时Promise的状态变成Fulfilled。代码接着执行遇到console.log(2),输出2。
5.接着执行,代码遇到then方法,其回调函数做为micro-task入栈,进入Promise的任务队列之中,此时Promise的then 里面的function回调函数跟setTimeout里面的function回调函数有着殊途同归之意,都会被放到各自的任务队列中,
 直到函数上下文即script中全部的非队列代码执行完毕后再执行,并且微任务队列优先于宏任务队列被处理,
整体顺序为:上下文非队列代码>微任务队列回调函数代码>宏任务队列回调函数代码
6.代码接着执行,此时遇到console.log(3),输出3。
7.输出3以后第一个宏任务script的代码执行完毕,这时候开始开始执行全部在队列之中的micro-task。then的回调函数入栈执行完毕以后出栈,这时候输出5
8.这时候全部的micro-task执行完毕,第一轮循环结束。第二轮循环从setTimeout的任务队列开始,setTimeout的回调函数入栈执行完毕以后出栈,此时输出4。
最后,为了加深理解,再上一段代码:
console.log('golb1');
setTimeout(function() {
console.log('timeout1');
new Promise(function(resolve) { console.log('timeout1_promise'); resolve();
    setTimeout(function(){
      console.log('time_timeout')
    });   }).then(function() { console.log('timeout1_then') })
setTimeout(function() { console.log('timeout1_timeout1'); }); }) new Promise(function(resolve) { console.log('glob1_promise'); resolve();
  setTimeout(function(){
     console.log('prp_timeout')
    });
}).then(function() { console.log('glob1_then') }) 
若是你的执行结果是:golb1=>glob1_promise=>glob1_then=>timeout1=>timeout1_promise=>timeout1_then=>prp_timeout=>time_timeout=>timeout1_timeout1,可能异步队列算是入门了吧!~~上面的代码看起来有点杂乱,可能用asyns搭配await改造一下会更好,可是这或多或少是鄙人从setTimeout中获得的看法吧,有啥不对之处望指正:另外,参考了一下别人的文章:https://zhuanlan.zhihu.com/p/26238030写的确实不错
相关文章
相关标签/搜索