一、概念
一种计算机系统的运行机制,JavaScript采用这种机制,来解决单线程运行带来的一些问题
要理解Event Loop,就要从程序的运行模式讲起,运行以后的程序叫"进程",一般情况下,一个进程只能执行一个任务
如果有很多任务需要执行,不外乎三种解决方法
- 排队,因为一个进程一次只能执行一个任务,只好等前面的任务执行完了,再执行后面的任务。
- 新建进程,使用 fork 命令,为每个任务新建一个进程。
- 新建线程,因为进程太耗费资源,所以如今的程序往往一个进程可以包含多个进程,由线程去完成任务。
JavaScript是一种单线程语言,所有任务都在一个线程上完成,即采用上面的第一种方法。一旦遇到大量任务或者遇到一个耗时的任务,网页就会出现"假死",因为JavaScript停不下来,也就无法响应用户的行为。
JavaScript从诞生起就是单线程。原因大概是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。后来就约定俗成,JavaScript为一种单线程语言。(Worker API可以实现多线程,但是JavaScript本身始终是单线程的。)
二、功能
负责执行代码、收集和处理事件以及执行队列中的子任务
三、原理
1. 执行栈与事件队列
JavaScript代码执行的时候,不同的变量存放于内存中不同的位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。 但是我这里说的执行栈和上面这个栈的意义却有些不同。
当我们调用一系列方法的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方函数调用形成了一个由若干帧组成的栈(先进后出),称之为执行栈。当脚本开始执行的时候,代码同步压入栈中,函数方法从顶部开始执行,当前执行方法结束后,js会退出这个执行环境并销毁,然后进入到下一个执行环境(如果还有的话),这个过程会反复进行,直到全部执行完毕。这个过程是可以无限进行的,除非发生了栈溢出。这些就是同步代码执行。
如果发送一个异步请求的话,就涉及到事件队列了
js引擎遇到一个异步事件后,不会一直等待执行结果出来,而是将这个事件挂起,继续走执行栈中的其他任务。然后当这个异步事件结果返回来之后,js会将这个事件加入与当前执行栈不同的另一个队列,这个就叫事件队列(也就是异步事件返回结果时按先后顺序排列)。当它被放入事件队列中时不会立即执行其回调,而是等待当前执行栈中所有的任务都执行完毕了,主线程处于闲置的时候,才回去查找事件队列中是否有需要执行的任务,如果有,就会从找那个取出排在第一位的事件,把该事件对应的回调放入执行栈中,然后执行其同步代码,执行结束后,如果闲置了,再次查找,如此循环。这个过程就是“<font color="#e43938">事件循环(Event Loop)</font>”
2. 宏任务与微任务 (macro task && micro task)
事实上,异步任务之间也有执行顺序的区别,不用的异步任务分为两类:微任务(micro task) 和 宏任务(macro task)
属于宏任务的有:
setInterval()
setTimeout()
I/O
requestAnimationFrame (晚于微任务执行)
微任务:
new Promise()
new MutationObserver()
process.nextTick()
重点:当前执行栈执行完毕时,会先处理所有微任务队列中的事件,然后再去宏任务队列事件,同一次事件循环中,微任务永远在宏任务之前执行
console.log(1);
setTimeout(()=> {
console.log(2);
process.nextTick(()=> {
console.log(3);
});
new Promise(resolve=> {
console.log(4);
resolve()
}).then(()=> {
console.log(5);
})
})
process.nextTick(()=> {
console.log(6);
})
new Promise(resolve=> {
console.log(7);
resolve()
}).then(()=> {
console.log(8);
})
setTimeout(()=> {
console.log(9);
process.nextTick(()=> {
console.log(10);
})
new Promise(resolve=> {
console.log(11);
resolve()
}).then(()=> {
console.log(12);
})
})
console.log(13);
// 1 7 13 6 8 2 4 3 5 9 11 10 12
一个 web worker 或者一个跨域的 iframe 都有自己的栈、堆和消息队列。两个不同的运行时只能通过 postMessage 方法进行通信。如果另一个运行时侦听 message 事件,则此方法会向该运行时添加消息。
四、最后
以上基本输入常识内容,还有浏览器与node环境的部分差异,这些都没做深入研究,暂时做个笔记了结。后续发现问题订正
---------------2020.04.10
相关内容链接:https://www.ruanyifeng.com/blog/2013/10/event_loop.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop