上一节我们讲了js是单线程,但在数据请求上会有一些问题,通常我们会用ajax异步的去请求数据,也能保证我们项目正常的运行,那js中异步模式的原理有是怎么样的呢?
js虽然是单线程,但也提供了一个任务队列,利用任务队列我们可以模拟一个多线程,里面放的任务都是一些异步的操作。我们在函数的章节讲过,决定函数的执行顺序是又call stask(调用栈),可js不仅仅有函数,还有其他的模式,而其他模式是又称事件循环。
任务队列(消息队列)
1、是一个有序的任务集合列表(放的都是异步任务),在js中任务队列可以有多个;
任务队列分类:
task(macrotask)宏任务队列 | microtask( 微任务队列) |
---|---|
DOM操作 | Promise |
用户交互(事件) | process.nextTick(nodejs) |
网络任务(ajax) | MutationObserver(H5里监听DOM节点变化) |
history traversal任务(h5当中的历史操作) | |
定时器 |
2、当代码里有异步操作的时候,就会把异步操作分发到对应的任务对列里;
它们的执行顺序也是不同的,先走微任务,后走宏任务。那任务队列的代码是怎么放进去的?其实是有一个任务源。
3、每一个任务都会有任务源;
任务源是用来分发任务的,根据任务的不同来决定是放在宏任务还是微任务。
4、一个队列里可以放多个任务,任务的读取方式为先进先出,后进后出;
栗子一:
setTimeout(()=>{
console.log('老沙最慢');
},100);
setTimeout(()=>{
console.log('八戒第二');
},0);
console.log('猴哥最快');
上面的代码是怎么执行的呢?
当代码运行的时候,先创建一个globalEC,代码继续运行的时候发现一个定时器,定时器就是一个任务源,它会把定位器代码放入宏任务中,但定时器有一个100毫秒的时间,意思是100毫秒后才把定期器代码放到宏任务中,接着往下走,又发现一个定时器,而定时的时间为0毫秒,所以会先把‘八戒第二’这个任务放到宏任务中,此时所有的代码依然没有执行,代码要执行还得需放入call stack中执行,代码继续往下走,发现了“console.log('猴哥最快')”,而“console.log('猴哥最快');”并不是宏任务或者微任务,则直接放到call stack中,然后执行,一旦执行完成,就从call stack中弹出,然后开始执行微任务,但是此时,微任务什么也没有,就开始执行宏任务,先把定时器中“ 八戒第二” 先放到call stack中执行,执行完成后弹出,最后在把“ 老沙最慢”的定时器放入call stack 中执行。所以会依次打印 “ 猴哥最快 八戒第二 老沙最慢”
事件循环 Event Loop
1、它是HTML规范中定义的或者是DOM中定义的,并不是ECMAScript定义的;
参考(打不开的话需翻墙)
1、https://www.w3.org/TR/html5/webappapis.html#event-loops
2、https://html.spec.whatwg.org/#event-loops
2、它包括两种,一种在浏览器上下文里,一种在web workers里(HTML5);
3、它是一种分配模式,把任务队列里的任务分配给全局执行上下文进行执行;
4、执行顺序;
1、先把微任务队列里的任务拿出来交给全局执行上下文执行;
2、再把宏任务队列里的任务拿出来交给全局执行上下文执行;
3、不断的循环;
注意:
1、任务队列(异步代码)里的代码最终还是要放到全局执行上下文(同步的代码)里去执行;
2、当全局执行上下文里的代码都执行完了才去执行任务队列里的代码;
3、宏任务与微任务的区别是:宏任务会再下一次的Event Loop里执行,微任务会在本次Event Loop里执行;
栗子二:
console.log('全局的');
setTimeout(()=>{
console.log('定时器的');
},0);
new Promise((resolve,reject)=>{
console.log('Promise的');
resolve();
}).then(()=>{
console.log('then的');
});
以上代码运行的时候,首先发现一个“console.log('全局的');”,会首先放入到call stack里执行,然后继续运行的时候,发现一个定时器,会把定时器放入到宏任务当中,继续运行的时候,又发现了一个new Promise,在new Promise的回调函数中的“ console.log('定时器的');”会放入全局执行栈中立即执行,resolve()也会放到全局执行栈中,而Promise的then代码放入到微任务当中,先从微任务中把“console.log('then的');”发入call stack中执行,执行完成之后再弹出去,此时微任务中已经没有代码可以执行了,微任务 事件循环已经完成,此时一遍任务循环一节完成了,然后再进行第二次任务循环,开始执行宏任务 的代码,所以打印的结果依次是“全局的、Promise的、then的、定时器的”。