js Event Loop

194 阅读2分钟

三个概念:

  • 执行栈:js代码执行的一个封闭环境,在执行栈内部,代码都是同步执行的,如果遇到异步任务,则将其推入任务队列;
  • 宏任务队列(MacroTask):setTimeout、setInterval、setImmediate或者I/O等异步操作的回调任务存放在该队列;
  • 微任务队列(microTask):Promise、Mutation Observer、process.nextTick等异步操作的回调任务存放在该队列。

流程:

代码首先在执行栈中执行,遇到宏任务或者微任务,直接将其推入对应的队列中,后续调用。执行栈代码执行完毕后,栈空,优先清空微任务队列,每一个微任务内部的代码在执行时,又相当于进入了一个新的执行栈,后续过程与之前相同。直到微任务队列为空,此时我们去将宏任务队列中最先入队的任务揪出来,进入一个新的执行栈,该栈为空时,还是会优先清空微任务队列(如果此次执行又推入新的微任务的话)

这就是事件循环的过程。

清晰解读

首先明确几个概念:

  • js引擎线程(执行js代码)
  • 定时器触发线程(负责在指定时间延迟后将回调函数推入事件队列)
  • 异步http请求线程(在请求结束后将回调函数推入事件队列)
  • 事件触发线程(维护一个事件队列)

流程:

js引擎线程在执行栈中执行同步代码,如果遇到setTimeout/setInterval,会通知定时触发器线程,而定时器线程在间隔时间后会通知事件触发线程,将回调函数推入事件触发线程维护的事件队列中。当js引擎线程空闲时,会询问事件触发线程,是否有未执行的事件,有则将其推入执行栈。

GUI渲染线程

js引擎线程互斥,两者只能同时运行一个。浏览器将两者的运行机制搞得很明白。 当js引擎线程空闲时,GUI渲染线程会立即执行,进行页面的渲染更新。

例子:

new Promise(resolve => { 
  resolve(); 
}).then(() => { 
  console.log('then1'); 
  console.log('推1-1入队'); 
  new Promise(resolve => { 
    resolve(); 
  }).then(() => { 
    console.log('then1-1'); 
    console.log('推1-2入队'); 
  }).then(() => { 
    console.log('then1-2') 
  }); 
  console.log('推2入队'); 
}).then(() => { 
  console.log('then2'); 
  console.log('推2-1入队'); 
  new Promise(resolve => { 
    resolve(); 
  }).then(() => { 
    console.log('then2-1'); 
    console.log('推2-1-1入队'); 
    new Promise((resolve) => { 
      resolve(); 
    }).then(() => { 
      console.log('then2-1-1'); 
      console.log('推2-1-2入队'); 
    }).then(() => { 
      console.log('then2-1-2') 
    }); 
    console.log('推2-2入队'); 
  }).then(() => { 
    console.log('then2-2'); 
    console.log('推2-3入队'); 
  }).then(() => { 
    console.log('then2-3'); 
  }); 
  console.log('推3入队'); 
}).then(() => { 
  console.log('then3'); 
  console.log('推3-1入队'); 
  new Promise(res => { 
    res(); 
  }).then(() => { 
    console.log('then3-1'); 
    console.log('推3-2入队'); 
  }).then(() => { 
    console.log('then3-2'); 
  }); 
});

大家可以去执行试试看。

bye。