Event Loop 和消息队列

Event Loop and 消息队列

1.我们都知道javascript是一门单线程、异步、非阻塞、解析类型脚本语言

  • 单线程
  • 异步
  • 非阻塞
  • 事件循环 消息队列 微任务 宏任务

2.单线程与多线程

javascript 的设计就是为了处理浏览器网页的交互(DOM操作处理,UI动画等)决定了他是一门单线程语言。如果有多个线程同时在操作DOM,那网页将会是一团糟 ,由此我们就可以知道处理任务是一件接着一件处理,从上网下执行的

3.JS引擎线程

其实,JavaScript 单线程指的是浏览器中负责解释和执行JavaScript代码的只有一个线程,但是浏览器的渲染进程是提供多个线程的,如下:

  • JS引擎线程
  • 事件触发进程
  • 定时触发器线程
  • 异步Http请求线程
  • GUI渲染进程(GUI渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被冻结了。)
    当遇到计时器、DOM事件监听或者是网络请求的任务时,JS引擎会将它们直接交给 webapi,也就是浏览器提供的相应线程(如定时器线程为setTimeout计时、异步http请求线程处理网络请求)去处理,而JS引擎线程继续后面的其他任务,这样便实现了 异步非阻塞。
    定时器触发线程也只是为 setTimeout(…, 1000) 定时而已,时间一到,还会把它对应的回调函数(callback)交给 任务队列 去维护,JS引擎线程会在适当的时候去任务队列取出任务并执行。
    JS引擎线程什么时候去处理呢?消息队列又是什么?

4.事件循环与消息队列

JavaScript 通过事件循环 Event Loop 的机制来解决这个问题

其实 事件循环 机制和 任务队列 的维护是由事件触发线程控制的。

事件触发线程 同样是浏览器渲染引擎提供的,它会维护一个 任务队列。

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

同时,JS引擎线程会维护一个 执行栈,同步代码会依次加入执行栈然后执行,结束会退出执行栈。

如果执行栈里的任务执行完成,即执行栈为空的时候(即JS引擎线程空闲),事件触发线程才会从消息队列取出一个任务(即异步的回调函数)放入执行栈中执行。

消息队列是类似队列的数据结构,遵循先入先出(FIFO)的规则。

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一但"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复,这种机制就被称为事件循环(event loop)

blockchain

上面说到了异步,JavaScript 中有同步代码与异步代码。,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

5.宏任务(macro-task)和微任务(micro-task)

  1. 宏任务:主代码块,setTimeout setTimeval等(可以看到,事件队列中的每一个都是宏任务,现在被称为宏任务队列)
  2. 微任务:Promise process.nextTick等

JS引擎线程首先执行主代码块。

每次执行栈执行的代码就是一个宏任务,包括任务队列(宏任务队列)中的,因为执行栈中的宏任务执行完会去取任务队列(宏任务队列)中的任务加入执行栈中,即同样是事件循环的机制。

在执行宏任务时遇到Promise等,会创建微任务(.then()里面的回调),并加入到微任务队列队尾。

micro-task必然是在某个宏任务执行的时候创建的,而在下一个宏任务开始之前,浏览器会对页面重新渲染(task >> 渲染 >> 下一个task(从任务队列中取一个))。同时,在上一个宏任务执行完成后,渲染页面之前,会执行当前微任务队列中的所有微任务。

也就是说,在某一个macro-task执行完后,在重新渲染与开始下一个宏任务之前,就会将在它执行期间产生的所有micro-task都执行完毕(在渲染前)

执行机制:

执行一个宏任务(栈中没有就从事件队列中获取)
执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
渲染完毕后,JS引擎线程继续,开始下一个宏任务(从宏任务队列中获取)

blockchain

一个event loop有一个或者多个task队列。task任务源非常宽泛,比如ajax的onload,click事件,基本上我们经常绑定的各种事件都是task任务源,还有数据库操作(IndexedDB ),需要注意的是setTimeout、setInterval、setImmediate也是task任务源。总结来说task任务源:

script
setTimeout
setInterval
setImmediate
I/O
requestAnimationFrame
UI rendering
微任务 micro-task(Job)
microtask 队列和task 队列有些相似,都是先进先出的队列,由指定的任务源去提供任务,不同的是一个 event loop里只有一个microtask 队列。另外microtask执行时机和Macrotasks也有所差异

process.nextTick
promises
Object.observe
MutationObserver
宏任务和微任务的区别
宏队列可以有多个,微任务队列只有一个,所以每创建一个新的settimeout都是一个新的宏任务队列,执行完一个宏任务队列后,都会去checkpoint 微任务。
一个事件循环后,微任务队列执行完了,再执行宏任务队列
一个事件循环中,在执行完一个宏队列之后,就会去check 微任务队列

eg

new Promise((resolve) => {
      console.log('new Promise(macro task 1)');
      resolve();
    }).then(() => {
      console.log('micro task 1');
      setTimeout(() => {
        console.log('setTimeout1');
      }, 500)
    })

    setTimeout(() => {
      console.log('setTimeout2');
    }, 100)

    console.log('========== 结束==========');


async function f1() {
      await f2()
      console.log('f1结束')
    }
    async function f2() {
      await f3()
      console.log('f2结束')
    }
    async function f3() {
      console.log('f3结束')
    }
    f1()
    new Promise(res => {
      console.log('new Promise')
      res()
    }).then(res => {
      console.log('promise第一个then')
    }).then(res => {
      console.log('promise第二个then')
    })

console.log('1');

    setTimeout(function () {
      console.log('2');
      Promise.resolve().then(function () {
        console.log('3');
      })
      new Promise(function (resolve) {
        console.log('4');
        resolve();
      }).then(function () {
        console.log('5')
      })
    })
    Promise.resolve().then(function () {
      console.log('6');
    })
    new Promise(function (resolve) {
      console.log('7');
      resolve();
    }).then(function () {
      console.log('8')
    })

    setTimeout(function () {
      console.log('9');
      Promise.resolve().then(function () {
        console.log('10');
      })
      new Promise(function (resolve) {
        console.log('11');
        resolve();
      }).then(function () {
        console.log('12')
      })
    })
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值