1.事件循环
JavaScript
运行是单线程的,浏览器在执行js脚本的时候,当js中的一环报错或者出现死循环,就不会再继续往下执行了,直到这个问题被解决。
相关概念
- 队列机制
JavaScript
提供了一种机制,会将所有的定时器都放到一个队列里面,当主线程的代码执行完成之后再执行队列里面的代码
- 宏任务
所有在主线程上执行的任务(除了异步函数以外的所有任务); 对应的微任务即:异步函数/异步任务
- 异步函数
如果一个异步函数被调用时,该函数会立即返回,尽管该函数规定的操作任务还没有完成
js执行过程
- 首先执行宏任务 ,当执行宏任务的时候,如果发现有定时器任务时,会将定时器添加到宏任务的任务队列里面,继续完成宏任务;
- 当宏任务完成后,会检查是否有微任务:如果有,则执行后,再回到微任务,如果没有,则回到宏任务;
- 检查任务队列里有没有未完成的任务,如果有,则继续执行,执行后,结束js脚本的执行;如果没有则结束js脚本的执行。
setTimeout( () => console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then( () => {
console.log(3)
})
Promise.resolve(5).then(() => console.log(5))
console.log(2)
// 输出结果为:1,2,3,5,4
上述执行过程为:
- 首先执行同步代码,第一行的
setTimeout
是异步代码,跳过,来到了new Promise(...)
这一段代码。这种方式是一个构造函数,是一个同步代码,所以执行同步代码里面的函数,即console.log(1)
- 接下来是一个
then
的异步,跳过。再往下,是一个Promise.then()
的异步,跳过。最后一个是一段同步代码console.log(2)
。所以,第一轮打印了1, 2两个值。 - 接下来进入下一步,即异步代码。从上往下,第一个是
setTimeout
,还有两个是Promise.then()
。setTimeout
是宏任务的异步,Promise.then()
是微任务的异步,微任务是优先于宏任务执行的, - 所以,此时会先跳过
setTimeout
任务,执行两个Promise.then()
的微任务。所以此时会执行console.log(3)
和console.log(5)
两个函数,最后执行console.log(4)
。
事件循环Event Loop
EventLoop
是一个执行模型,在不同的有不同的实现,浏览器和NodeJS基于不同的技术实现了各自的EventLoop
。
执行宏任务的同步任务,在主线程上形成一个执行栈,查看执行栈是否为空,如果执行栈为空,就会去检查微任务队列是否为空,如果为空的话,就执行Task
(宏任务),否则就一次性执行完所有微任务。
每次单个宏任务执行完毕后,检查微任务队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务后,设置微任务队列为null,然后再执行宏任务,如此循环。
2.执行环境
浏览器的Event loop
是在HTML5中定义的规范,而node中则由libuv库实现。libuv库流程大体分为6个阶段:timers,I/O callbacks,idle、prepare,poll,check,close callbacks
,和浏览器的microtask
,macrotask
有区别。两者执行的规则不同,所以不相同。
- 浏览器的EventLoop是在HTML5规范中明确定义了的
NodeJS的EventLoop是基于libuv实现的。可以在libuv官网和NodeJS官网查看
libuv
已经对NodeJS
的EventLoop
做出了实现,但是浏览器的HTML5规范只是定义了EventLoop
的实现模型,具体的实现留给了浏览器厂商。
3.宏任务(macrotask )和微任务(microtask )
在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask
的队列(这个队列也被叫做 task queue
)中取出第一个任务,执行完毕后取出 microtask
队列中的所有任务顺序执行;之后再取 macrotask
任务,周而复始,直至两个队列的任务都取完。
宏任务的主要工作
- 创建主文档对象,解析HTML,执行主线或者全局的
javascript
的代码,更改url以及各种事件 - 页面加载,输入,网络事件,定时器。从浏览器角度看,宏任务是一个个离散的,独立的工作单元
- 运行完成后,浏览器可以继续其他调度,重新渲染页面的UI或者去执行垃圾回收
一些异步任务的回调会以此进入 macrotask queue
(宏任务队列),等等后续被调用,这些异步函数包括:setTimeout,setInterval,setImmediate (Node),requestAnimationFrame (浏览器),I/O,UI rendering (浏览器)
.
微任务的工作
- 微任务是更小的任务,微任务更新应用程序的状态,但是必须在浏览器任务继续执行其他任务之前执行,浏览器任务包括重新渲染页面的UI。
- 微任务包括
Promise
的回调函数,DOM发生变化等,微任务需要尽可能快地,通过异步方式执行,同时不能产生全新的微任务。 - 微任务能使得我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,UI重绘会使得应用状态不连续
- 另一些异步回调会进入
microtask queue
(微任务队列) ,等待后续被调用,这些异步函数包括:process.nextTick (Node)
,Promise.then()
,catch
,finally
,Object.observe
,MutationObserver
.
4.总结
由于浏览器引擎负责解释和执行JavaScript
的主线程是单线程,同步执行一个耗时较大的任务会导致阻塞。
异步执行代码可以解决阻塞问题,但会带来顺序的不确定性,如果这些任务彼此不相关,就不一定需要交互,如果没有相互影响的话,不确定性是完全可以接受的。
如果你需要保证异步执行的顺序,比如依次远程读取一系列url, 按顺序触发动画等,就需要嵌套很多层回调函数。
多级嵌套的回调函数弊端很多:不直观,强耦合,回调的不确定性,不利于维护与复用等。所以才有promise
等异步执行的相关方法,保证执行的顺序性。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!