理解JS事件循环(Event Loop)

理解Event Loop

javascript是单线程。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

于是js所有任务分为两种:同步任务,异步任务

同步任务

调用立即得到结果的任务,同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

console.log('a');
 for (let i = 0; i <5 ; i++) {
       console.log(i)
 }
 console.log('b');
 // a 0 1 2 3 4 5 b 

异步任务

调用无法立即得到结果,需要额外的操作才能预期结果的任务,异步任务不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

setTimeout(() => {
     console.log("加入任务队列:0s")
 },0)
 ​
 document.onclick = () => {
     console.log("加入任务队列:onclick")
 }
 ​
 setTimeout(() => {
     console.log("加入任务队列:1s")
 },1000) 

JS引擎遇到异步任务(DOM事件监听、网络请求、setTimeout计时器等),会交给相应的线程单独去维护异步任务,等待某个时机(计时器结束、网络请求成功、用户点击DOM),然后由 事件触发线程 将异步对应的 回调函数 加入到消息队列中,消息队列中的回调函数等待被执行。

运行机制如下:

1.所有同步任务都在主线程上执行,形成一个[执行栈]2.主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。3.一旦"执行栈"中的所有同步任务执行完毕,就会读取"任务队列",开始执行。> 如何读取任务队列?> > 主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)4.主线程不断重复上面的第三步。举个例子

 console.log('script start')
 ​
 setTimeout(() => {
   console.log('timer 1 over')
 }, 1000)
 ​
 setTimeout(() => {
   console.log('timer 2 over')
 }, 0)
 ​
 console.log('script end') 

同步和异步的执行机制在ES5的情况下够用了,但是ES6会有一些问题。

宏任务(MacroTask/Task)和微任务(microtask)

宏任务:script全部代码、计时器、Ajax、读取文件、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN

微任务:Process.nextTick(Node独有)、Promise.then

EventLoop

也就是我们经常使用异步的原理,事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制。

Node与浏览器的 Event Loop 差异

浏览器和 Node 环境下,microtask 任务队列的执行时机不同

  • Node 端,microtask 在事件循环的各个阶段之间执行
  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

执行顺序

1.同步任务2.process.nextTick> process.nextTick()虽然它是异步API的一部分,但从技术上讲,它不是事件循环的一部分。> > 它将 callback 添加到next tick队列。 一旦当前事件轮询队列的任务全部完成,在next tick队列中的所有callbacks会被依次调用。> > 换种理解方式:> > * 当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。3.微任务4.宏任务5.setmmediateJS 引擎会将所有任务按照类别分到这两个队列中,首先在 宏任务 的队列中取出第一个任务,执行完毕后取出 微任务 队列中的所有任务顺序执行;之后再取 宏任务,周而复始,直至两个队列的任务都取完。

 <script> // 宏任务
     
     console.log('script start');
 ​
 setTimeout(function () {
 console.log('setTimeout');
 }, 0);
 ​
 new Promise((resolve) => {
 console.log('promise0');
 resolve('promise1');
 }).then((res) => {
 console.log(res);
 }).then(function () {
   console.log('promise2');
 });
 ​
 console.log('script end'); </script> 

1.script :宏任务, push到宏任务队列,执行栈读取到有一个宏任务开始执行。| 名称 | 值 || — | — || Tasks(宏任务) | script || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log ||2.console.log :同步任务,立即执行,打印‘script start’。| 名称 | 值 || — | — || Tasks(宏任务) | script || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log | script start |3.setTimeout:宏任务, 0s后push到任务队列。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log | script start |4.new : 同步任务,立即执行,打印‘promise0’,并返回promise1。> new Promise 相当于创建一个对象,是立即执行的| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log | script start ,promise0 |5.Promise.then : 微任务, push到微任务队列。> 第二个Promise.then是第一个Promise.then执行后才调用,故不会继续push| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) | script || Log | script start ,promise0 |6.console.log:同步任务,立即执行,打印‘script end’’。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) | script || Log | script start ,promise0,script end |7.script执行完成出栈,JS执行栈pop。准备去执行微任务| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1 |8.执行微任务(即将微任务回调加入JS执行栈),打印‘script end’’后调用Promise.then,。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) | Promise callback || Log | script start ,promise0,script end,promise1 |9.Promise.then : 微任务, push到微任务队列。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then,Promise2.then || JS stack(JS执行栈) | Promise callback || Log | script start ,promise0,script end,promise1 |10.Promise1.then执行完成,JS执行栈pop, 微任务队列pop。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise2.then || JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1 |11.微任务队列内还有值继续执行,执行和Promise1.then一样,打印‘promise2’后发现没有后续;则Promise2.then执行完成,JS执行栈pop, 微任务队列pop。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1,promise2 |12.script执行完成,由它而引发的微任务执也行完成,此时可以认为宏任务script执行完成,宏任务队列pop。| 名称 | 值 || — | — || Tasks(宏任务) | setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1,promise2 |13.执行下一个宏任务,JS执行栈push,打印‘setTimeout’| 名称 | 值 || — | — || Tasks(宏任务) | setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) | setTimeout callback || Log | script start ,promise0,script end,promise1,promise2,setTimeout |14.宏任务setTimeout执行完成,JS执行栈pop,宏任务队列pop| 名称 | 值 || — | — || Tasks(宏任务) ||| Microtasks(微任务) ||| JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1,promise2,setTimeout |### 异步编程的几种方案

观察者模式,又叫发布/订阅模式

假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。

jQuery.subscribe("done", f2);
 ​
 function f1(){
   setTimeout(function () {
     // f1的任务代码
     jQuery.publish("done") ;   // f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
   }, 1000);
 }
 ​
 jQuery.unsubscribe("done", f2);  // f2完成执行后,也可以取消订阅(unsubscribe)
 ​ 
Promise对象

优点:将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

缺点:

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
var promise = new Promise(function(resolve, reject) {
   // ... some code
 ​
   if (/* 异步操作成功 */){
     resolve(value);
 } else {
     reject(error);
 }
 });
 // Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
 promise.then(function(value) {
   // success
 }, function(error) {
   // failure
 });
 ​ 
async与await

async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。 async函数返回一个 Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async function timeout(ms) {
   await new Promise((resolve) => {
     setTimeout(resolve, ms);
 });
 }
 ​
 async function asyncPrint(value, ms) {
   await timeout(ms);
   console.log(value);
 }
 ​
 asyncPrint('hello world', 50);
 ​ 
```**最后一句**学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。* [完全熟练掌握 eventLoop](https://link.juejin.cn/?target=https%3A%2F%2Fjakearchibald.com%2F2015%2Ftasks-microtasks-queues-and-schedules%2F "https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/")

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值