1. 什么是事件循环
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的 这种运行机制又称为Event Loop(事件循环)。
我认为是一种JavaScript模拟多线程机制,用以提高代码执行效率
2. 为什么要用事件循环
JavaScript的单线程机制:
- 在早期Js是一个单线程的语言纯单线程语言,因为: 比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节 点,这时浏览器应该以哪个线程为准?所以这就需要引入一个机制解决这个问题

- 上图的绿色部分是程序的运行时间,红色部分是等待时间。可以看到,由于I/O操作很慢,所以这个线程的 大部分运行时间都在空等I/O操作的返回结果。这种运行方式称为"同步模式"(synchronous I/O)或"堵塞模式"(blocking I/O)。
这样耗费了大量的cpu时间,使效率下降
引入事件循环机制

- 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是
子线程完全受主线程控制
,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。 - 有了这样一个机制,就可以提高代码的运行效率
3. 事件循环详解
机制详解
主线程运行的时候,产生堆和栈,栈中的代码调用各种外部API,异步操作执行完成后,就在消息队列中排队。只要栈中的代码执行完毕,主线程就会去读取消息队列,依次执行那些异步任务所对应的回调函数

详解如下:
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在一个"消息队列"。只要异步操作执行完成,就到消息队列中排队
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取消息队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行
- 主线程不断重复上面的第三步

实例1
console.log(1)
div.onclick = () => {console.log('click')}
console.log(2)
setTimeout(() => {console.log('timeout')},1000)
执行过程
-
执行第一行代码,第一行是一个同步任务,控制台显示1
-
执行第二行代码,第二行是一个异步任务,发起异步请求,可以在任意时刻执行鼠标点击的异步操作
-
执行第三行代码,第三行是一个同步任务,控制台显示2
-
执行第四行代码,第四行是一个异步任务,发起异步请求,1s后执行定时器任务
-
假设从执行第四行代码的1s内,执行了鼠标点击,则鼠标任务在消息队列中排到首位
-
从执行第四行代码1s后,定时器任务到消息队列中排到第二位
-
现在同步任务已经执行完毕,则从消息队列中按照次序把异步任务放到执行栈中执行
-
则控制台依次显示'click‘、'timeout'
-
过了一段时间后,又执行了一次鼠标点击,由于消息队列中已经空了,则鼠标任务在消息队列中排到首位
-
同步任务执行完毕后,再从消息队列中按照次序把异步任务放到执行栈中执行
-
则控制台显示'click'
实例2
setTimeout(function(){console.log(1);}, 0);
console.log(2);
- 输出结果:2 1
- 因为定时器是异步操作,等待同步操作全部进行完毕才执行代码,这也就说明了所有的定时器都是不准确的,因为它并不是在定时器代码开始的时候立即执行,要等待所有同步代码执行完毕后它才执行,所以说要比定时器的预定时间长。
实例3
div.onclick = function fn(){console.log('click')}
- 一次异步过程的详解:
-
主线程通过调用异步函数div.onclick发起异步请求
-
在某一时刻,执行异步操作,即鼠标点击
-
接着,回调函数fn到消息队列中排队
-
主线程从消息队列中读取fn到执行栈中
-
然后在执行栈中执行fn里面的代码console.log('click')
-
于是,控制台显示'click'
4. 何来“事件”和“循环”
事件
我们可以直接把这个“事件”理解为“任务”
但是为什么叫事件循环?而不叫任务循环或消息循环。究其原因是消息队列中的每条消息实际上都对应着一个事件 DOM操作对应的是DOM事件,资源加载操作对应的是加载事件,而定时器操作可以看做对应一个“时间到了”的事件
循环
主线程不停的去查询事件队列中是否有新的事件,这就是一个循环
从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后到消息队列中排队。程序按照代码顺序执行完毕后,查询消息队列中是否有等待的消息。如果有,则按照次序从消息队列中把消息放到执行栈中执行。执行完毕后,再从消息队列中获取消息,再执行,不断重复。
5. 一些扩展
异步操作的类型
-
宏任务(macrotasks): setTimeout, setInterval, setImmediate, I/O, UI rendering
-
微任务(microtasks): process.nextTick, Promises, Object.observe(废弃), MutationObserver
不同的任务类型在调用时的不同
当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
宏任务和微任务不同的实例
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
结果 2 3 1