JavaScript事件执行机制(1)

256 阅读3分钟

JavaScript 事件执行机制包括:任务队列(Tasks)、微任务队列(MicroTasks)和调用栈;队列是先入先出的数据结构,栈是先入后出的数据结构;执行顺序是全局Script-> 微任务(可能渲染页面)->宏任务;全局Script为同步任务,只执行一次,由于JavaScript为单线程编程语言,同步任务执行过程中,异步任务只能等待其执行完毕。

  • 任务队列(Tasks):包括了全局Script和宏任务;setTimeout属于宏任务

  • 微任务队列(MicroTasks):每次执行完微任务队列后可能会渲染页面;**调用栈必须清空才会执行微任务队列中的微任务;**Promise.then(catch)、obsever属于微任务

加上注释代码打印:'script start', 'script end', 'promise1', ’fail‘,'promise3', 'promise2', 'promise4'

未加上注释代码打印:'script start', 'script end', 'promise1', 'promise2'

console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(function () {
    console.log('promise1');
  })
  .then(function () {
    console.log('promise2');
  });

  /* 注释  
Promise.resolve('success').then((res)=>{
                  console.log(res);
            }).then((res)=>{   
                  console.log('success2');        
            });         
Promise.reject('fail').catch((res)=>{   
                   console.log(res); 
            });            
Promise.resolve('success3').then((res)=>{   
                   console.log(res);       
            }).then((res)=>{          
                   console.log('success4');  
          });
*/
console.log('script end');

这里先分析未加上注释代码:

  • 执行代码console.log('script start');该代码输入全局Script任务,加入任务队列,将全局Script任务加入队列调用栈,输出'script start'

  • 执行代码setTimeout(function ()),此时任务队列再加入setTimeout宏任务,但是宏任务必须等待上一轮微任务队列执行完成后才能执行,因此这里仍然是输出'script start'

  • 执行代码Promise.resolve().then(function ()),此时微任务队列加入promise.then微任务,微任务必须等待本轮任务队列(全局Script/宏任务)执行完成后再执行,因此这里仍然是输出'script start'

  • 执行代码console.log('script end');此时由于全局Script任务已存在,调用栈也是全局Script,因此这里输出'script start', 'script end'。此时代码从上到下已经执行完一次了,代表全局Script任务执行完成,接下来移除全局Script任务

  • 本轮任务队列已经执行完成,发现调用栈为空。接下来,调用栈将加入微任务并执行,输出'script start', 'script end', 'promise 1'

  • 发现执行完上面的微任务后,该微任务移除出调用栈,又回调了一个微任务,于是将新的微任务加入微任务队列,并加入调用栈,调用栈执行完后输出'script start', 'script end', 'promise 1', 'promise 2'

  • 微任务队列执行完毕,移除微任务队列和调用栈,全局Script也调用完成,根据队列先入先出原则,在任务队列中移除全局Script任务。此时页面可能渲染一次,准备进入下一个周期(宏任务->微任务)

  • 任务队列执行宏任务,宏任务setTimeout回调函数,将该任务加入调用栈中执行,输出'script start', 'script end', 'promise 1', 'promise 2', 'setTimeout',执行后,将宏任务移除出调用栈,发现没有微任务队列中没有微任务,于是也移除出任务栈,代码执行完毕!

思考:

1.分析加入上述注释代码

由于微任务队列是先入先出原则,这个”出“其实是代表调用栈调用的顺序,链式调用的产生新的微任务都是排在队列的后面,更晚输出,所以每段promise代码的同级then都是依次输出,再依次向下输出

    ****2.在setTimeout中加入微任务会发生什么?

在执行栈调用宏任务时,发现微任务,将其加入微任务队列中;执行完成宏任务后,将宏任务移除出任务队列之前,发现微任务队列里还有微任务,执行栈调用执行微任务并将该任务移除;最后微任务队列移除微任务,宏任务队列移除宏任务,代码执行结束。这里先输出setTimeout结果再输出微任务结果