博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
事件循环(the Event Loop)、宏任务(macrotask)、微任务(microtask)
阅读量:5873 次
发布时间:2019-06-19

本文共 2919 字,大约阅读时间需要 9 分钟。

开篇

我们都知道JavaScript是单线程的语言,它不像大多数语言可以开启多线程,当处理一些阻塞并且很慢的操作时,它可以通过多线程使操作变成异步(例如读取文件等IO操作)。其实JavaScript也有同步异步的区分。首先来看一下同步与异步的区别

// 同步alert('阻塞中...')console.log('待执行')// 异步setTimeout(() => {	alert('阻塞中...')}, 1000)console.log('待执行')复制代码

从上面俩张图可以看到区别,alert()同步会阻塞应用,导致下面代码只好等待其完成,而setTimeout异步则不需要等待,log可以先执行。那刚才说JavaScript没有多线程,它又是如何实现异步操作的呢,这里就要说到事件循环啦。

正题

事件循环(the Event Loop)

事件循环会有一个事件的执行栈,当我们调用一个函数,它的地址、参数、局部变量都会压入这个栈中,当运行完之后,他们又会依次的出栈。我们拿一个例子来展示下这个过程。

// 平方function square(num) {    return num * num}// 平方求和function sumSqrt(a, b) {     return square(a) + square(b)}function log(a, b) {    console.log(sumSqrt(a, b))}log(2, 3) // 13复制代码

上面的例子是传入俩个参数打印出他们的平方和,当运行log函数时,该函数入栈,发现需要求平方和时,sumSqrt函数入栈,之后,发现需要先计算平方,square函数入栈。当square执行完返回结果后,出栈,以此类推。整个事件循环就是依次入栈,再依次出栈。具体流程图如下:

通过上图应该很容易理解JavaScript的执行栈是如何运作的,那要实现异步该如何做呢,第一步就是不要停留在执行栈中,因为执行栈是入栈出栈操作,停留久了就会造成阻塞。所以此时就要说到第二个概念任务队列

当浏览器遇到需要异步操作时,会把它放入任务队列去执行,执行成功后,会将回调函数返回给主线程。

那是不是任务队列中的异步操作执行完成后,会立即将回调函数入栈从而执行呢,答案当然是否定的,如果是那样,整个程序运行的岂不是很乱套。之所以叫事件循环是因为它是有一个周期的概念,每次循环代表一个周期的。


在这个周期内,主线程执行栈正常工作,当主线程任务清空时,会从任务队列中提取到已完成的异步回调函数入栈,然后执行栈又开始工作,进入下一次事件循环。具体什么意思呢,我们还是用代码配合图来演示整个事件循环周期:

这里要注意的是:每次事件循环只会从任务队列中获取一个回调函数,无论回调队列中有多少个函数,都只会有一个推到主线程。其他的函数需要等到下一次事件循环(主线程任务又清空时)。举个例子:

console.log('script start')setTimeout(() => {    console.log('异步操作1')}, 1000)setTimeout(() => {    console.log('异步操作2')}, 1000)console.log('script end')/* 执行结果 * script start * script end * 异步操作1 * 异步操作2 */复制代码

放到chrome控制台执行,虽然结果上看似同时打印异步操作1、异步操作2,但其实打印“异步操作1”的回调函数先被推入主线程,当log执行完后,又进入下一次事件循环,把打印的“异步操作2”的函数推入主线程。所以这里延伸了一个问题:

setTimeout不一定是在规定时间内后立即执行。如上述例子,1000ms只代表多长时间后进入回调队列,但什么时候去执行它,要看主线程的任务什么时候结束。

在这里推荐大家一个事件循环可视化网址 ,在这里可以自己写函数做测试,加深对事件循环机制的印象。弹窗的视频也是非常的赞,推荐大家跟着视频实践一下。

宏任务(macrotask)、微任务(microtask)

其实上面所讲的任务队列存放的任务都叫宏任务,宏任务是指时间耗时比较长的。

宏任务(macrotask): setTimeoutsetIntervalsetImmediate,I/OUI rendering

微任务(microtask):process.nextTickPromisesObject.observeMutationObserver

俩者的区别在于在事件循环中,每次只会执行一个macrotask,而所有microtask都会依次执行直到为空。并且每次主线程任务被清空时,先执行所有microtask,再去执行一个macrotask

console.log('script start');setTimeout(function() {  console.log('setTimeout1');  setTimeout(function() {	console.log('setTimeout2');  })}, 0);Promise.resolve().then(function() {  console.log('promise1');}).then(function() {  console.log('promise2');});console.log('script end');/** * script start * script end * promise1 * promise2 * setTimeout1 * setTimeout2*/复制代码

上面事件循环的整体流程是:


Cycle 1

  1. 打印'script start'
  2. setTimout(fn, 0)被调度到任务队列去执行
  3. Promise.resolve()被列为microtask
  4. 打印'script end'
  5. stack 清空 microtasks 执行
  6. 打印'promise1'
  7. .then()被列为microtask,继续执行microtasks
  8. 打印'promise2'

Cycle 2

  1. microtasks 队列清空 setTimout 的回调可以执行
  2. 打印'setTimeout1'
  3. 回调函数内setTimout(fn, 0)被调度到任务队列去执行

Cycle3

  1. setTimout 的回调可以执行
  2. 打印'setTimeout2'

我们用图片在演示一遍:

总结

  • JavaScript是单线程语言,通过事件循环和任务队列来实现异步操作。
  • 异步操作都会放到任务队列中去执行,执行完毕后,回调函数等待被调度
  • 主线程任务清空时,首先会执行所有的microTask,然后执行一个macroTask
  • setTimeout不会按照给定时间后执行(主线程任务阻塞时,会一直等待)
  • microTask异步也可能造成程序阻塞。(因为每次事件循环会执行所有microTask,死循环)

参考

转载地址:http://athnx.baihongyu.com/

你可能感兴趣的文章
飞秋无法显示局域网好友
查看>>
学员会诊之03:你那惨不忍睹的三层架构
查看>>
vue-04-组件
查看>>
Golang协程与通道整理
查看>>
解决win7远程桌面连接时发生身份验证错误的方法
查看>>
C/C++ 多线程机制
查看>>
Android JNI 学习(四):接口方法表 & Base Api & Exception Api
查看>>
工控随笔_06_西门子_Step7归档项目无法备份的解决方法
查看>>
js - object.assign 以及浅、深拷贝
查看>>
信息系统开发平台OpenExpressApp - 订单示例(Getting Started)
查看>>
Windows7主题包:Aero Pure 0.2 更像Win8
查看>>
Windows XP Embedded 官方下载地址
查看>>
SQL Azure(九) 把本地的SQL Server数据库迁移到SQL Azure云数据库上
查看>>
Galaxy Note 通过Kies升级,固件存放目录在哪里?
查看>>
高效代码审查的十个经验
查看>>
JSVSIP
查看>>
【转】Qt数据库总结
查看>>
flex DataGroup
查看>>
centos 安装sftp服务
查看>>
MySQL技术内幕读书笔记(七)——锁
查看>>