JS并发模型与Event Loop

1. JS单线程执行

JS的执行是单线程的,也就是同一段时间只能作一件事。单线程执行意味着全部的任务须要排队,只有前一个任务执行完毕,才能执行后一个任务。为了充分提升JS执行的效率,全部的任务分为同步任务和异步任务。html

1)同步任务ajax

同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完,才能执行后一个任务。浏览器

2)异步任务bash

异步任务指的不进入主线程,而是进入“任务队列”(task queue)里的任务。只有"任务队列"通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。并发

那么JS是如何处理这些同步任务和异步任务呢?看下面介绍JS执行的并发模型。dom

2. 事件循环

JavaScript的并发模型基于“事件循环”机制。异步

1)Stack 执行栈函数

函数调用造成了一个栈帧。当函数被调用时,会建立一个执行帧,帧中包含该函数的参数和局部变量,若是该函数内部还有函数调用,就会接着建立另外一个帧,包含内部函数的参数和局部变量。若是存在全局函数调用,则全局函数压在帧的最底部。当函数执行完毕后,函数所在的执行帧会被弹出栈外,函数所在的变量和做用域会被释放。执行栈事件先进后出。oop

2)Heap 堆ui

Heap指存放对象的内存区域,对象会被分配在一个堆中,即用以表示一大块非结构化的内存区域。在函数内使用对象,实际上是指向内存中对象的一个指针。

3)Queue 队列

一个 JavaScript 运行时包含了一个待处理的任务队列,任务队列是先进先出。每个任务都关联着一个用以处理这个任务的函数。在事件循环期间的某个时刻,JS运行时会从任务队列中取出最早进入队列中的一个任务进行处理。这个任务会被移出队列,任务关联的函数会在执行栈中建立新的栈帧,被执行。执行栈清空后,事件循环会再处理队列中的下一条任务。

当一个任务须要太长时间才能处理完毕时,能够缩短任务处理,或者将一个任务裁剪成多个任务。在浏览器里,当一个事件发生且有一个事件监听器绑定在该事件上时,任务会被随时添加进队列。

setTimeout(fn, delay),delay参数是指任务被实际加入到队列中的最小延迟时间,若是队列中没有其余任务,在这个延迟时间以后,任务会立刻被处理。可是若是有其余任务,setTimeout会等待其余任务处理完后再执行fn。

总之,队列中的这些任务会在主线程的执行栈被清空时被依次读取(任务队列先进先出,即先被压入队列中的事件会被先执行)。

下面的图更详细的介绍JS的事件循环模型。

主要操做逻辑以下:

1)执行栈执行主线程任务,当有操做dom,ajax交互,定时器等操做时,这些任务会被移入到callback queue 任务队列中。

2)当主线程任务执行完毕为空时,会读取callback queue队列中的函数,进入主线程执行。

3)上述过程会不断重复,造成Event Loop。

4. Macrotasks 和 Microtasks

在一个事件循环中,异步事件返回结果后会被放入到任务队列中。根据异步事件的类型,会被放入到 对应的宏任务列表或者微任务列表中,当执行栈为空的时候,主线程会首先查看微任务队列中的事件,执行微任务队列中的事件后,再执行宏任务队列中的事件。

1)Macrotasks 宏任务

包括 setTimeout, setInterval, setImmediate, I/O, UI rendering

2)Microtasks 微任务

包括 process.nextTick, Promises, Object.observe(废弃), MutationObserver

console.log('1');
setTimeout(function() {
  console.log('2');
}, 0);
Promise.resolve().then(function() {
  console.log('3');
}).then(function() {
  console.log('4');
});
console.log('5');

//输出值
1
5
3
4
undefined
2
复制代码

microtask会优先macrotask执行。在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空。

参考资料

JavaScript并发模型与Event Loop

并发模型与事件循环

JavaScript 运行机制详解:再谈Event Loop

相关文章
相关标签/搜索