前言
Event Loop
是计算机系统的一种运行机制,是个很重要的概念。而 Javascript
用这种机制来解决单线程运行带来的问题。理解很熟悉将会有利于我们更容易理解 Vue
的异步事件。
JavaScript 是单线程的
1、什么是单线程?
单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。简单来说,即同一时间只能做一件事件。
2、Js 为什么是单线程?
Js
是一种运行在网页的简单的脚本语言,由于设计的初衷是作为浏览器脚本语言,用于与用户互动,以及操作DOM
。这决定它是单线程的。
3、单线程带来的问题?
单线程就意味着,所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就需要一直等着。这就会导致 IO 操作(耗时但 cpu 闲置)时造成性能浪费的问题。
4、如何解决单线程的性能问题?
采用异步可以解决。主线程完全可以不管 IO 操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。于是,所有任务可以分成两种,一种是同步任务,另一种是异步任务。
执行栈和任务队列
执行栈
当执行某个函数、用户点击一次鼠标,Ajax 完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入执行栈队列中,等待主线程读取, 遵循先进先出原则。
任务队列
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列。需要注意的是异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
事件循环(Event Loop)
JavaScript 内存模型
在了解事件循环之前,先要弄明白 Js 的内存模型,这有助于更好的理解事件循环。
- 调用栈(Call Stack):用于主线程任务的执行。
- 堆(Heap):用于存放非结构数据,如程序分配的变量和对象。
- 任务队列(Queue): 用于存放异步任务。
Js 异步执行的运行机制
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
任务队列
异步任务分为 宏任务(macrotask)与微任务(microtask),不同的 API 注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。
- 宏任务主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
- 微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
我们的 JavaScript 的执行过程是单线程的,所有的任务可以看做存放在两个队列中——执行队列和事件队列。
执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。
当 JavaScript 执行时,优先执行完所有同步代码,遇到对应的异步代码,就会根据其任务类型存到对应队列(宏任务放入事件队列,微任务放入执行队列之后,事件队列之前);当执行完同步代码之后,就会执行位于执行队列和事件队列之间的微任务,然后再执行事件队列中的宏任务。
实例
new Promise(resolve => {resolve(1);
Promise.resolve().then(() => {
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
这段代码的流程大致如下:
- script 任务先运行。首先遇到 Promise 实例,构造函数首先执行,所以首先输出了 4。此时 microtask 的任务有 t2 和 t1
- script 任务继续运行,输出 3。至此,第一个宏任务执行完成。
- 执行所有的微任务,先后取出 t2 和 t1,分别输出 2 和 1
- 代码执行完毕
综上,上述代码的输出是:4321
事件循环
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 Event Loop(事件循环)。
从上图我们可以看出:
- 主线程运行的时候,产生堆(heap)和栈(stack)。
- 栈中的代码调用各种外部 API,它们在 ” 任务队列 ” 中加入各种事件(click,load,done)。
- 栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数。
小结
事件循环其实并不难,多查阅资料,多看看相关例子就 ok。希望一知半解的童鞋抓紧学习。
相关文章
- JavaScript 中的事件循环 Event Loop
- 再谈 Event Loop
- 从一道题浅说 JavaScript 的事件循环