浏览器的事件循环机制是HTML中定义的规范。
JavaScript有一个主线程和调用栈,所有的任务都会被放到调用栈等待主线程执行。
- JS调用栈
是一种先进后出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈的顶部移除该函数,直到栈内被清空。
- 同步任务、异步任务
JS单线程任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果之后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。
- 事件循环机制
调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。
- 定时器
定时器会开启一条定时器触发线程来触发计时,定时器会在等待了指定的时间后将事件放到任务队列中等待主线程执行。定时器指定的延时毫秒数其实并不准确,因为定时器只是在到了指定的时间将事件放到任务队列中,必须要等到同步的任务和现有的任务队列中的事件全部执行完成之后,才会去读取定时器的事件到主线程执行,中间可能会存在耗时比较久的任务,那么就不可能保证在指定的时间执行。
- 宏任务、微任务
除了广义的同步任务和异步任务,JavaScript单线程中的任务可以细分为宏任务和微任务。
- 宏任务:script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI rendering
- 微任务:process.nextTick,Promises,Object.observe,MutationObserver
补充:在node环境下,process.nextTick的优先级高于Promise,也就是可以简单理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才会执行微任务中的Promise部分。
第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否存在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。
console.log(1); setTimeout(function() { console.log(2); }) var promise = new Promise(function(resolve, reject) { console.log(3); resolve(); }) promise.then(function() { console.log(4); }) console.log(5);
- 上面的示例中,第一次事件循环,整段代码作为宏任务进入主线程执行。
- 遇到了 setTimeout ,就会等到过了指定的时间后将回调函数放入到宏任务的任务队列中。
- 遇到 Promise,将 then 函数放入到微任务的任务队列中。
- 整个事件循环完成之后,会去检测微任务的任务队列中是否存在任务,存在就执行。
- 第一次的循环结果打印为: 1,3,5,4。 宏任务-微任务
- 接着再到宏任务的任务队列中按顺序取出一个宏任务到栈中让主线程执行,那么在这次循环中的宏任务就是 setTimeout 注册的回调函数,执行完这个回调函数,发现在这次循环中并不存在微任务,就准备进行下一次事件循环。
- 检测到宏任务队列中已经没有了要执行的任务,那么就结束事件循环。
- 最终的结果就是 1,3,5,4,2。
谢谢大佬的总结,本文是截取 街边微凉小悲伤 的文章,原版请参考下方链接
https://www.cnblogs.com/yqx0605xi/p/9267827.html
原文地址:https://www.cnblogs.com/danew/p/11546331.html