我理解的javascript事件循环(一)

189 阅读3分钟

javascript事件循环分为2种:一种是浏览器端事件循环,一种是node端事件循环。 此文只是捋一捋我对浏览器端事件循环的理解。

前言

我们都知道 JavaScript 是一门单线程语言,这意味着同一时间只能执行一个任务,结束了才能去执行下一个。如果前面的任务没有执行完,后面的任务就会一直等待。试想,有一个耗时很长的网络请求,如果所有任务都需要等待这个请求完成才能继续,显然是不合理的并且我们在浏览器中也没有体验过这种情况(除非你要同步请求 Ajax),究其原因,是 JavaScript 借助异步机制来实现了任务的调度。

JavaScript 常见的异步有3种:ajax、dom事件(button的点击事件等)、定时器(setTimeout等)。

举个例子

console.log("A");

setTimeout(() => {
  console.log("B");
}, 100);

console.log("C");

稍微了解一点浏览器中异步机制的同学都能答出会输出 “A C B”,我们来通过分析 event loop 来对浏览器中的异步进行梳理,并搞清上面的问题。

从上图我们可以看出js分为3个部分:stack(主线程)、WebAPIs、event loop。

浏览器的执行顺序是:

1.先执行主线程,即

console.log("A");

setTimeout(() => {
  console.log("B");
}, 100);

console.log("C");

程序从上往下执行,但是执行到setTimeout时,只是注册了一个WebAPIs事件,并不会执行setTimeout的回调函数。 此回调函数是异步的,会在主线程的所有程序执行完毕后根据注册的时间才执行。

2.当主程序执行完毕后,就执行event loop。当然callback queue一开始是空的。event loop是一个‘死循环’,它会一遍又一遍的去检测WebAPIs有没有可执行的回调函数,有就把回调函数放入自身的callback queue并执行,没有的话继续下一次循环。

3.浏览器监听WebAPIs事件。过了100ms定时器事件触发了,它会通知event loop有可执行的回调函数了。

事件循环的一个必须条件是 执行栈中的任务执行完毕才会执行 “任务队列”中的任务,如果执行栈一直处于执行不完的情况,也就没有任务队列什么事了。所以我们的settimeout 后面的参数其实是不准确的,他开始算的时间应该是执行栈的任务执行完毕。

settimeout还有一个最小时间4ms的规定,所以当我们写settimeout(function(){},0)时,其中的0其实是4ms。

微任务和宏任务

首先说明,是以__浏览器为处理环境__下的执行逻辑 浏览器环境下的微任务和宏任务有哪些 宏任务:setTimeout setImmediate MessageChannel 微任务:Promise.then MutationObserver 记住两点:

  • 微任务在宏任务之前的执行,先执行 执行栈中的内容 执行后 清空微任务
  • 每次取一个宏任务 就去清空微任务,之后再去取宏任务

题目

setTimeout(function(){
    console.log('setTimeout1');
    Promise.resolve().then(()=>{
        console.log('then1');
    });
},0)
Promise.resolve().then(()=>{
    console.log('then2');
    Promise.resolve().then(()=>{
        console.log('then3');
    })
    setTimeout(function(){
        console.log('setTimeout2');
    },0)
})
  • 微任务宏任务同时出现,先将微任务放入执行队列,栈为空则先执行then2。setTimeout1放入宏任务执行队列中
  • then2之后执行后,接下来存在微任务then3。将then3放入微任务队列中。
  • 接下来setTimeout2加入到宏任务队列中。
  • 此时执行栈为空,执行then3。
  • 微任务全部执行完毕后,执行宏任务setTimeout1,执行发现微任务then1,放置到微任务队列中。
  • setTimeout1宏任务执行完,再次清空微任务队列,执行then1
  • 微任务全部执行完毕后,执行宏任务setTimeout2。程序结束。