事件循环存在的作用
在使用js 编写的应用程序中,往往会存在同步代码和异步代码,在执行js 程序的过程中,同步代码会直接执行,异步代码会直接跳过,并在恰当的时机执行,但是js 是单线程环境运行的,js线程并不会再开启一个线程去处理我们的异步代码,那么js 是在哪里运行异步代码的?异步代码执行完毕后又是如何将结果告知给当前线程?js是用什么机制来实现同步代码与异步代码能在单线程环境中运行的呢?实际上,js 是通过事件循环机制来实现了同步代码与异步代码的和谐共存。事件循环的存在就是为了保证我们编写的js中的同步代码与异步代码能够在单线程环境下能够合理运行而存在
js代码的执行流程
js 代码是从全局代码开始执行的,在执行过程中,如果遇到异步任务就会产生事件循环的流程,如果没有异步任务,则会进行如下的流程js代码的执行流程(同步代码)。
js中的异步代码是在哪里执行的?
js 中的异步代码实际上并不是在我们常说的js 线程中运行的,在js 线程中执行的代码是异步代码执行后的结果的处理代码。 我们编写的js 代码是由js引擎解析并执行的,js引擎是运行在浏览器中的,但是浏览器中不只包含我们的js 引擎,还包含一些其他线程,用来处理绘制,网络请求等等一些其他事务。假如我们把js引擎运行的线程称为js 线程,浏览器中负责其他事务的线程的称为其他线程,那么异步代码实际上是在浏览器的其他线程中运行的,当异步代码执行完毕后,如果我们需要异步代码执行的结果,浏览器会通过中断的方式告知js 线程,异步代码执行的结果。然后我们就可以在js 线程中拿到异步代码的结果,进行后续的处理。
js 事件循环过程
其实在js 线程和其他线程间还存在另外一层,这个层被称为event loop,负责事件循环的处理的也是一个线程,称为event loop线程,这个线程负责将js线程发起的异步函数调用派发给其他web API线程,同时在异步函数调用有结果时,负责将结果加入到事件队列中。js 引擎在执行代码时,遇到异步代码就会通过event loop 派发,这个过程被称为注册函数或者发起函数,同时 event loop 会维护着对这个注册函数的生命周期,一旦这个函数有了结果,event loop 会将这个函数对应的回调函数加入到事件队列中,等待js 线程的 执行。事件循环就是主线程重复从消息队列中取消息、执行的过程。
宏任务与微任务
在es6 之前 ,我们添加异步任务主要是如下类型
- script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
这些任务都称之为宏任务。
在ES6引入了promise后,产生了一个新的名词”微任务(microtask)“。微任务的执行顺序与之前我们所说的任务(我们可以称之为”宏任务“)是不同的。
- 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
- 任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
- macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
- micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(H5新特性)
- setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
- 来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。
- 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
- 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。
JavaScript 对异步任务的处理方式
1.回调函数
2.Promise
3.Promise + generator
4.async/await
详情请移步