JS 事件机制 Event Loop

谈谈Event Loop

不管是浏览器的Event loop 仍是 Nodejs 中的Event loop,都是基于JS 的单线程设计而生的(纯属我的理解),经过事件循环实现非阻塞异步执行效果。node

几个概念

谈到事件机制首先弄清楚主线程执行栈任务队列这几个概念。web

主线程:运行JS 代码。JS 的单线程针对的是单一的主线程,不是只能有一个线程。编程

执行栈:关于执行栈的概念结合执行上下文来看,《JavaScript深刻之执行上下文栈》promise

任务队列Task Queue:即队列,是一种先进先出的数据结构浏览器

三者的关系是:主线程要执行的都在执行栈里,执行栈里的内容是从任务队列里拿过来的。bash

关于任务

任务能够划分为宏任务微任务数据结构

  • 宏任务MacroTask: 整个script , setTimeout, setInterval, setImmidiate(浏览器暂时不支持,只有IE10,可是在nodejs 中经常使用到)、I/O、UI Redering。异步

  • 微任务(MicroTask):Promise, Async/Await(底层就是Promise)、Process.nextTick (node独有)、MutationObserver。async

具体执行时,任务还有另外一种的划分就是同步任务异步任务函数

对于同步任务来讲 ,放入执行栈里按序依次执行的,可是对于异步任务而言,是在异步任务有告终果以后将异步任务的回调函数放到任务队列里等待主线程空闲时执行。注意这里是在执行栈中若是遇到异步任务,是注册异步任务的回调函数 ,把回调函数放到任务队列中(这一步会产出任务)。

浏览器中的Event Loop

执行流程

事件循环的进程模型

  • 执行栈一开始认为是空,将宏任务: 总体的script 压入栈执行。
  • 执行过程当中遇到同步任务按序一步一步执行,遇到异步任务注册异步任务的回调函数放到对应的任务队列中(这里有宏任务队列,微任务队列)。这样就产生了新的macro-task 和 micro-task。
  • script 中代码执行完,将script 出栈,也是一次宏任务出栈 。
  • 检测任务队列中是否有微任务,清理微任务队列。这里须要注意的是宏任务出队是一个一个出,而微任务是一队一队出。
  • 更新页面渲染
  • 检测是否有web worker 任务,处理web worker。
  • 上述过程循环往复,直到两个队列都清空。

总结一下总体的流程:

主线程首先会执行一个宏任务,当此宏任务执行完以后,会去查看是否有微任务队列。若是有,那就清空整个微任务队列。若是没有,会去查看宏任务队列,取宏任务队列中的第一个去执行,执行宏任务的过程当中遇到微任务,依次加入微任务队列等待下次执行。(固然执行微任务的时候也会产生宏任务,主线程会放入宏任务队列)。栈空后,再次读取任务队列中的任务,以此类推。

举个例子

console.log('script start')
 setTimeout(() => {
     console.log('setTimeout 1');
     Promise.resolve().then(() => {
          console.log('promise1')
    })
}, 0)

 new Promise(resolve => {
    console.log('promise2');
    setTimeout(() => {
        console.log('setTimeout2');
        resolve();
    }, 0)
 })

 Promise.resolve().then(() => {
     console.log('promise3');
     setTimeout(() => {
          console.log('setTimeout 3')
    },0)
 }).then(() => console.log('promise4'))
 console.log('script end');
复制代码

输出结果

script start 
promise2
script end
promise3
promise4
setTimeout1
promise1
setTimeout2
setTimeout3
复制代码

详细执行流程以下:

1. 初始宏任务队列只有总体script,执行栈、微任务队列皆为空

宏任务script入栈执行:

输出 script start

碰见setTimeout1放入宏任务队列

执行同步代码输出 promise2,碰见setTimeout2放入宏任务队列

碰见微任务promise3promise4放入微任务队列

输出 script end

咱们看下第一个宏任务执行完的状态:

宏任务队列: setTimeout1setTimeout2

微任务队列: promise3promise4


2. 接下来去清理微任务队列

微任务队列遵循先进先出原则,promise3入栈执行:输出 promise三、,将setTimeout3放入宏任务队列。promise3出栈 ,promise4入栈执行,输出 promise4

清理完微任务后的状态:

宏任务队列setTimeout1setTimeout2setTimeout3

微任务队列:空


3. 从宏任务队列中取出第一个入栈执行

setTimeout1 入栈执行,输出 setTimeout1 ,将 promise1放入微任务队列

本次执行完的状态:

宏任务队列setTimeout2setTimeout3

微任务队列promise1


4. 清理微任务队列

输出 promise1

本轮执行完状态:

宏任务队列setTimeout2setTimeout3

微任务队列: 空


五、 从宏任务队列中取出第一个执行

输出 setTimeout2

本轮执行完状态:

宏任务队列setTimeout3

微任务队列: 空


6. 微任务队列为空,继续从宏任务队列取出第一个执行

输出 setTimeout3

至此宏任务队列微任务队列都为空。

从上述执行过程当中, 不难看出,浏览器的Event Loop 老是一个宏任务 ——> 一队微任务 这种顺序依次循环

另外一个例子

var a = async function () {
    await Promise.resolve().then(() => console.log(111));
    console.log(222)
}
a().then(() => console.log(333))

var b = async function () {
    await setTimeout(() => console.log('aaa'), 0);
    console.log('bbb')
}
b().then(() => console.log('ccc'))

var c = async function () {
    await console.log('A');
    console.log('B')
}
c().then(() => console.log('C'))

复制代码

输出结果:

A -> 111 -> bbb -> B -> 222 -> ccc -> C -> 333 -> aaa

执行顺序:

1. script入栈

执行同步代码,调用a():将111放入微任务队列。继续往下走执行b(),将aaa 放入宏任务队列,将 bbb 放入微任务队列,接着执行c(), 打印 A, 将B 放入微任务队列。

这次执行完的状态:

宏任务队列: aaa

微任务队列: 111bbbB

2. 清理微任务队列

执行 111, 打印111 、将222放入微任务队列,执行bbb:打印bbb并将 ccc放入微任务队列,执行B: 打印B 并将C放入微任务队列,此时微任务队列并无清理完,因此接着打印222cccC,打印完222333放置微任务队列,因此接着打印333

这次执行完的状态:

宏任务队列: aaa

微任务队列: 空

  1. 执行aaa, 打印出aaa

上述代码如果很差理解能够转化为promise的方式来理解,效果以下:

new Promise(resolve => {
   	Promise.resolve()
        .then(() => console.log(111))
        .then(() => { 
	    console.log(222)
	    resolve()
	})
})
.then(() => console.log(333))

new Promise(resolve => {
	setTimeout(() => console.log('aaa'), 0)
	Promise.resolve().then(() => {
            console.log('bbb')
            resolve()
	})
	
})
.then(() => console.log('ccc'))

new Promise(resolve => {
	console.log('A')
	Promise.resolve()
	.then(() => {
	    console.log('B')
	    resolve()
	})
})
.then(() => console.log('C'))

复制代码

但这纯属我的理解,如有问题,还请大神指导。

Nodejs中的Event Loop

nodejs

1.简介

Node 中的 Event Loop和浏览器中的是彻底不相同的东西。Node.js采用V8做为js的解析引擎,而I/O处理方面使用了本身设计的libuvlibuv是一个跨平台、专门写给nodejs的库,使用异步,事件驱动的编程方式,核心是提供I/O的事件循环和异步回调。封装了不一样操做系统一些底层特性,对外提供统一的API。

2.六个阶段

未完待续...

相关文章
相关标签/搜索