浅析Event Loop(事件循环)

前言

本篇文章是作者在学习'事件循环'的一个总结,观看本文,你会对事件循环这种运行机制有所了解,并且准备了大量习题复习消化内容

Event Loop(事件循环)的由来

Event Loop(事件循环)是为了解决单线程问题而产生的‘计算机系统’一种运行机制。

JavaScript从诞生起就是单线程。所有任务都在一个线程上完成。一旦遇到大量任务或者遇到一个耗时的任务,网页就会出现"假死",无法响应用户的行为。为了解决这个问题,浏览器开始支持异步处理,就是把一些异步任务(AJAX/定时器...),放到任务队列中,然后通过不断读取、触发任务队列中的异步代码,这种机制就叫做事件循环(Event Loop)。

这里要注意:
1、单线程任务分为同步任务和异步任务,异步任务又分为宏任务(MacroTask)和微任务(MicroTask)

想深入了解线程的小伙伴可以看看下面阮一峰写的进程和线程的理解

线程的一个简单解释

常见的宏任务、微任务(如果不了解先记一下)

宏任务

script(全局任务)、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI rendering。

微任务

Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver(具体使用方式查看这里)

事件循环的执行过程

1、执行srcipt 里面的代码(要注意,srcipt是一个宏任务)
2、从上至下执行,同步代码直接执行,微任务会注册相应的回调函数,注册完成后将回调函数丢到微任务队列中,将宏任务丢到宏任务队列中
3、执行完全部同步代码,在进入微任务,按照2的规则运行
4、在执行下一个宏任务前之前会检查是否存在微任务,不存在才会执行下一个宏任务
5、执行宏任务的流程,按照1-4的顺序进行执行

总结:从script开始,任务按顺序进入执行栈中,同步任务在主线程中直接被执行,遇到异步任务时,异步任务会进入异步处理模块并注册相应的回调函数,注册完成后回调函数进入任务队列(遇到微任务进入微任务队列,遇到宏任务开启一个新的宏任务队列),待同步任务执行完执行栈为空时,依次进入栈中执行,每次执行宏任务之前会检查是否存在微任务,如果存在则将微任务队列中的所有任务执行完后再执行宏任务

这里要注意:
1、队列是先进先出,举个例子:当一个宏任务里面有2个微任务,那么在执行完同步任务后,先运行的是先入队列的那个微任务,然后在依次运行下一个进队列的微任务

从代码方面去理解(本篇文章的事件循环只考虑浏览器)

第一题(理解上面的执行过程)

// 同步代码
console.log(1);
// 宏任务代码
setTimeout(() =>{
    console.log(2)
},0)
// 微任务代码
new Promise((resolve)=>{
    console.log(3)
    resolve();
}).then(()=>{
    console.log(4)
})
console.log(5)
点击此获得答案 答案是: 1 3 5 4 2

解析

1、从上至下,运行同步代码,输出1
2、将宏任务丢到宏任务队列中
3、微任务注册相应的回调函数(所以输出了3),注册完成后将回调函数丢到微任务队列中
4、运行同步代码,输出5
4、本轮宏任务执行完毕,在执行下一个宏任务前,先看看还有没有微任务没执行完,这时看到微任务的回调函数.then(()=>{console.log(4)}) 没执行完,就给它执行输出 4
5、执行下一个宏任务,输出2
最终结果得到  1 3 5 4 2

微任务习题

习题一

const chongjing = new Promise((resolve, reject) => {
  console.log('2')
})
console.log('1', chongjing);
点击此获得答案 答案是: 2 1 Promise{}(Promise对象)

解析

1、从上至下,先遇到new Promise,执行该构造函数中的代码,输出 2
2、执行同步代码 输出1
3、此时chongjing没有被resolve或者reject,因此状态还是pending  输出 Promise{<pending>}

习题二

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);

点击此获得答案 答案是: 1 2 4
和习题一相似,只不过在promise中并没有resolve或者reject,因此promise.then并不会执行,它只有在被改变了状态之后才会执行。

习题三

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
点击此获得答案 答案是: promise1 1 Promise{: 'resolve1'} 2 Promise{} resolve1

解析

1、从上至下,先执行new Promise,执行该构造函数中的代码promise1
2、碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
3、碰到promise1.then这个微任务,将它放入微任务队列
4、promise2是一个新的状态为pending的Promise
5、执行同步代码1, 同时打印出promise1的状态是resolved
6、执行同步代码2,同时打印出promise2的状态是pending
7、宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为resolved,执行它。

最后一习题

const fn = () => (new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
}))
fn().then(res => {
  console.log(res)
})
console.log(2)
// 看看答案是是多少
// 如果将fn()放到下面答案又是什么顺序呢
console.log(2)
fn().then(res => {
  console.log(res)
})
如果你看到了这里,那么你是一个很爱学习的小伙伴呢,准备好了嘛,下面的习题会比上面稍微复杂些哦,当然你要是理解了上面的题目,下面的这些也是撒撒水啦。

宏任务结合微任务习题

习题一

setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
点击此获得答案 答案是: start timer1 promise timer2

习题二

const promise = new Promise((resolve, reject) => {
    console.log(1);
    setTimeout(() => {
        console.log("timerStart");
        resolve("success");
        console.log("timerEnd");
    }, 0);
    console.log(2);
});
promise.then((res) => {
    console.log(res);
});
console.log(4);
点击此获得答案 答案是: 1 2 4 timerStart timerEnd success

解析

1、从上至下,遇到new Promise ,输出 1 
2、遇到宏任务将setTimeout丢到宏任务中,输出2
3、遇到微任务promise.then,因为无resolve,这里理解为先不执行
4、输出同步任务 4 
5、执行下一个宏任务setTimeout
6、输出同步任务  timerStart
7、遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列
8、输出同步任务  timerEnd
9、查找是否有微任务,输出 success

习题三

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');
点击此获得答案 答案是: start promise1 timer1 promise2 timer2

解析

这题主要是考,队列先进先出
1、从上至下执行,遇到微任务,丢微任务队列
2、遇到宏任务丢宏任务队列
3、遇到同步任务,输出 start
4、执行微任务,遇到同步任务,输出promise1
5、执行微任务,遇到宏任务 setTimeout,丢宏任务队列
6、微任务执行完了,执行下一个宏任务,遇到同步任务,输出 timer1
7、执行下一个宏任务,遇到微任务,丢微任务队列
8、执行本轮宏任务中的微任务,输出 promise2
9、微任务执行完了,执行下一个宏任务,遇到同步任务,输出 timer2

看了这么多的题目,我就留些作业给你们吧,小兄弟要好好研究哟

作业一

const promise1 = new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve("success");
   console.log("timer1");
 }, 1000);
 console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {
 throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
 console.log("timer2");
 console.log("promise1", promise1);
 console.log("promise2", promise2);
}, 2000);
既然看到这里了,那么久给本博客点点收藏吧。
本文题目参考的是掘金LinDaiDai_霖呆呆:https://juejin.cn/user/360295513463912

发表于: 作者:Promise

阅读此作者的 更多文章

Github 新浪微博 SegmentFault 掘金专栏