事件循环
1、关于javascript
javascript是一门 单线程 语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎!
2、javascript事件循环
既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:
JS分为同步任务和异步任务
同步任务都在主线程上执行,形成一个执行栈
主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。
宏任务:
macro)task(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。 浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下: (macro)task->渲染->(macro)task->... (macro)task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
微任务:
microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。 microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
运行机制:
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下: 执行一个宏任务(栈中没有就从事件队列中获取) 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行) 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
流程图如下:
两道题 让你全部理解事件循环
console.log('1') setTimeout(function() { console.log('2') process.nextTick(function() { console.log('3') }) new Promise(function(resolve) { console.log('4') resolve() }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6') }) new Promise(function(resolve) { console.log('7') resolve() }).then(function() { console.log('8') }) setTimeout(function() { console.log('9') process.nextTick(function() { console.log('10') }) new Promise(function(resolve) { console.log('11') resolve() }).then(function() { console.log('12') }) }) 第二题 async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end'); 注意: async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } 等同于 async function async1() { console.log('async1 start'); Promise.resolve(async2()).then(() => { console.log('async1 end'); }) }