《理解JS: 事件循环机制》

从面试题了解事件循环机制:
第一道:

//请写出输出内容 async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); }console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0)async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');

第二道:
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { //async2做出如下更改: new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); }console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0)async1(); new Promise(function(resolve) { console.log('promise3'); resolve(); }).then(function() { console.log('promise4'); }); console.log('script end');

第三道:
async function async1() { console.log('async1 start'); await async2(); //更改如下: setTimeout(function() { console.log('setTimeout1') },0) } async function async2() { //更改如下: setTimeout(function() { console.log('setTimeout2') },0) }console.log('script start'); setTimeout(function() { console.log('setTimeout3'); }, 0)async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');

第四道:
async function a1 () { console.log('a1 start') await a2() console.log('a1 end') } async function a2 () { console.log('a2') }console.log('script start')setTimeout(() => { console.log('setTimeout') }, 0)Promise.resolve().then(() => { console.log('promise1') })a1()let promise2 = new Promise((resolve) => { resolve('promise2.then') console.log('promise2') })promise2.then((res) => { console.log(res) Promise.resolve().then(() => { console.log('promise3') }) }) console.log('script end')

第五道:
setTimeout(function(){console.log(1)},0); new Promise(function(resolve,reject){ console.log(2); setTimeout(function(){resolve()},0) }).then(function(){console.log(3) }).then(function(){console.log(4)}); process.nextTick(function(){console.log(5)}); console.log(6);

如果不太理解其中所蕴含的道理,点击下:体现事件循环机制过程在线网站
如果能正确答出上面题的输出内容,并能给予背后原理的解释,那证明读者对于JS中事件循环机制了解得挺深入的哦~
之前小编对于JS中事件循环机制了解也是模棱两可,近来对此部分进行了进一步的学习,先以一张图来说明学习成果:
《理解JS: 事件循环机制》
文章图片

想要了解JS中的事件循环机制,咱们需要了解如下内容:
  • event loop:主线程从“任务队列”中循环读取任务。而循环读取任务的先后顺序,则取决于任务队列(Job queue)中对于不同任务读取规则的限定
  • Job queue:分为两种类型:macro-task和micro-task,即为宏任务和微任务,微任务的执行优先级高于宏任务
《理解JS: 事件循环机制》
文章图片

  • 浏览器是多进程的,JS是单线程的。浏览器除了JS引擎之外,还有Web APIs线程、GUI线程等, JS引擎在执行过程中,如果遇到相关的事件(DOM操作、AJAX请求、滚轮事件、鼠标点击事件、setTimeout等),并不会因此阻塞,它会将这些事件移交给Web APIs线程处理,而自己接着往下进行。
  • setTimeout:设置的时间是最小延迟时间,并不是确切的等待时间,实际上最小延迟>=4ms,小于4ms的被当做4ms
  • Web APIs:按照一定的规则将这些事件放入一个任务队列(Callback queue,也叫task queue)
  • 任务队列:在HTML标准定义中,任务队列的数据结构不是队列,而是Set集合
《理解JS: 事件循环机制》
文章图片

  • JS单线程:不想并行操作DOM,DOM树不是线程安全的,如果是多线程,会引起冲突。只有一个执行栈(call stack),在同一时刻,JS引擎只能做一件事情,并且只要运行就直到完成(run to complete)。如果是这样的话,那么我网页后端请求数据的时候,为什么我还能点击按钮、滚动页面呢?原因是因为浏览器是多进程的,如下图所示的事件循环模型:
《理解JS: 事件循环机制》
文章图片

说明:
1、JS线程负责处理JS代码,当遇到一些异步操作的时候,则将这些异步事件移交给Web APIs处理,自己则继续往下执行
2、Web APIs线程将接收到的事件按照一定的规则添加到任务队列中,宏事件添加到宏任务队列中,微事件添加到微任务队列中
3、JS线程处理完当前的所有任务以后(执行栈为空),它会先去微任务队列中获取事件,并将微任务队列中按照先进先执行的顺序一件件执行完毕,直到微任务队列为空后,再去宏任务队列中取出一个事件执行
(每次取完一个宏任务队列中的事件执行完毕后,都先检查微任务队列)
4、然后不断循环第三步。
  • new Promise:在使用new关键字来创建Promise对象时,传递给Promise的函数称为executor,当promise被创建的时候,executor函数会自动执行,而then里面的内容才是异步执行的部分。
  • async/await:async是Generator函数的语法糖,await关键字会将后面的内容包装成promise交给Web APIs处理,执行栈会跳出async函数继续执行,直到promise执行完并返回结果。
《理解JS: 事件循环机制》
文章图片


在这里,小编第一次也并没有理解async/await关键字的作用,在第二遍学习的时候理解了,请看如下示例:
源代码:
async function async1() { console.log('async1 start') await async2() console.log('async1 end') }async function async2() { console.log('async2') }

其实,根据await关键字的作用,会将后面的内容包装成promise,然后应该是如下模样:
async function async1(){ console.log('async1 start') new promise((reslove)=>{ console.log('async2') }).then(()=>{ console.log('async1 end') }) }

基本上在理解了如上相关内容后,对于JS中异步机制以及相关面试题有了深入的理解。
【《理解JS: 事件循环机制》】如下为开头面试题答案:
第一道:
script start async1 start async2 promise1 script end async1 end promise2 setTimeout

第二道:
script start async1 start promise1 promise3 script end promise2 async1 end promise4 setTimeout

第三道:
script start async1 start promise1 script end promise2 setTimeout3 setTimeout2 setTimeout1

第四道:
script start a1 start a2 promise2 script end promise1 a1 end promise2.then promise3 setTimeout

第五道:
//输出的是2 6 5 1 3 4//区别在于promise的构造中,没有同步的resolve ,因此promise.then在当前的执行队列中是不存在的, 只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,因此3,4最后输出。

巨人肩膀:
谈谈Event Loop中的Job queue
事件循环(event loop)以及异步执行顺序

    推荐阅读