javascript单线程
由于JavaScript是单线程语言,因此,在一个进程上,只能运行一个线程,而不能多个线程同时运行。也就是说JavaScript不允许多个线程共享内存空间。因此,如果有多个线程想同时运行,则需采取排队的方式,即只有当前一个任务执行完毕,后一个任务才开始执行
1. JS将任务分为两种,同步任务和异步任务。
2. 当主线程开始执行同步任务时,会创建一个“执行栈”,每一个同步任务排队执行,只有前一个任务执行完毕,才会执行下一个任务。
3. 当主线程上的所有同步任务执行完毕之后,也就是当“执行栈”为空时,主线程会去读取任务队列上的异步任务(回调函数),并将异步任务推入执行栈中开始执行。
4. 主线程不断重复第二、第三个步骤。
5.
Event Loop(事件循环)
主线程中的所有同步任务执行完毕,再读取任务队列中的异步任务,这个过程是循环不断的。所以,整个的这种运行机制称为Event Loop(事件循环)。
在异步任务中,又可以分为两种,macrotask(宏任务)和micro(微任务)。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,在当前执行栈为空的时候,主线程会查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
两个类别的具体分类如下:
- macrotask: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
- microtask: process.nextTick, Promise, MutationObserver
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种webAPIs,它们在任务队列中加入各种事件(click,load,keyup等)。只要栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数。
对于setTimeout定时器而言,setTimeout会在指定时间内任务队列添加一个回调函数,如果任务队列中没有其他任务,则这条任务会立即执行;否则,这个任务会不得不等到其他异步任务执行完毕之后,才会得到执行。因此,setTimeout指定的执行时间,只是一个最早可能发生的时间,并不能保证一定会在那个时间发生。
举个例子。由于我们知道微任务优先于宏任务执行。所以Promise对象会优先于setTimeout。
setTimeout(() => console.log(1))
new Promise(resolve => {
console.log(2); resolve(3)
}).then(res => console.log(res))
// 2 3 1
同步任务取决于函数的调用位置,不同的调用位置,进入执行栈的位置就不同,主线程执行的顺序就不同
异步任务的执行与函数的调用位置无关,只取决于执行栈的任务数量,当同步任务执行完毕之后,才会开始执行异步任务,并且遵循先进入任务队列的事件先执行的原则。
微任务宏任务
宏任务(macrotask )和微任务(microtask )
macrotask 和 microtask 表示异步任务的两种分类。
在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。
宏任务和微任务之间的关系
//先看个例子
setTimeout(() => {
//执行后 回调一个宏事件
console.log('内层宏事件3')
}, 0)
console.log('外层宏事件1');
new Promise((resolve) => {
console.log('外层宏事件2');
resolve()
}).then(() => {
console.log('微事件1');
}).then(()=>{
console.log('微事件2')
})
//打印结果
//外层宏事件1
//外层宏事件2
//微事件1
//微事件2
//内层宏事件3
首先浏览器执行js进入第一个宏任务进入主线程, 遇到 setTimeout 分发到宏任务Event Queue中
• 遇到 console.log() 直接执行 输出 外层宏事件1
• 遇到 Promise, new Promise 直接执行 输出 外层宏事件2
• 执行then 被分发到微任务Event Queue中
•第一轮宏任务执行结束,开始执行微任务 打印 ‘微事件1’ ‘微事件2’
•第一轮微任务执行完毕,执行第二轮宏事件,打印setTimeout里面内容’内层宏事件3’