javascript是单线程。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
于是js所有任务分为两种:同步任务,异步任务
调用立即得到结果的任务,同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
console.log('a'); for (let i = 0; i <5 ; i++) { console.log(i) } console.log('b'); // a 0 1 2 3 4 5 b
调用无法立即得到结果,需要额外的操作才能预期结果的任务,异步任务不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
setTimeout(() => { console.log("加入任务队列:0s") },0) document.onclick = () => { console.log("加入任务队列:onclick") } setTimeout(() => { console.log("加入任务队列:1s") },1000)
JS引擎遇到异步任务(DOM事件监听、网络请求、setTimeout计时器等),会交给相应的线程单独去维护异步任务,等待某个时机(计时器结束、网络请求成功、用户点击DOM),然后由 事件触发线程 将异步对应的 回调函数 加入到消息队列中,消息队列中的回调函数等待被执行。
1.所有同步任务都在主线程上执行,形成一个[执行栈]2.主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。3.一旦"执行栈"中的所有同步任务执行完毕,就会读取"任务队列",开始执行。> 如何读取任务队列?> > 主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)4.主线程不断重复上面的第三步。举个例子
console.log('script start')
setTimeout(() => {
console.log('timer 1 over')
}, 1000)
setTimeout(() => {
console.log('timer 2 over')
}, 0)
console.log('script end')
同步和异步的执行机制在ES5的情况下够用了,但是ES6会有一些问题。
宏任务:
script
全部代码、计时器、Ajax、读取文件、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN
)微任务:Process.nextTick(Node独有)、Promise.then
也就是我们经常使用异步的原理,事件循环,是指浏览器或
Node
的一种解决javaScript
单线程运行时不会阻塞的一种机制。Node与浏览器的 Event Loop 差异
浏览器和 Node 环境下,microtask 任务队列的执行时机不同
- Node 端,microtask 在事件循环的各个阶段之间执行
- 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
执行顺序
1.同步任务2.process.nextTick> process.nextTick()
虽然它是异步API的一部分,但从技术上讲,它不是事件循环的一部分。> > 它将 callback
添加到next tick
队列。 一旦当前事件轮询队列的任务全部完成,在next tick
队列中的所有callbacks
会被依次调用。> > 换种理解方式:> > * 当每个阶段完成后,如果存在 nextTick
队列,就会清空队列中的所有回调函数,并且优先于其他 microtask
执行。3.微任务4.宏任务5.setmmediateJS 引擎会将所有任务按照类别分到这两个队列中,首先在 宏任务 的队列中取出第一个任务,执行完毕后取出 微任务 队列中的所有任务顺序执行;之后再取 宏任务,周而复始,直至两个队列的任务都取完。
1.script :宏任务, push到宏任务队列,执行栈读取到有一个宏任务开始执行。| 名称 | 值 || — | — || Tasks(宏任务) | script || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log ||2.console.log :同步任务,立即执行,打印‘script start’。| 名称 | 值 || — | — || Tasks(宏任务) | script || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log | script start |3.setTimeout:宏任务, 0s后push到任务队列。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log | script start |4.new : 同步任务,立即执行,打印‘promise0’,并返回promise1。> new Promise 相当于创建一个对象,是立即执行的| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) | script || Log | script start ,promise0 |5.Promise.then : 微任务, push到微任务队列。> 第二个Promise.then是第一个Promise.then执行后才调用,故不会继续push| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) | script || Log | script start ,promise0 |6.console.log:同步任务,立即执行,打印‘script end’’。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) | script || Log | script start ,promise0,script end |7.script执行完成出栈,JS执行栈pop。准备去执行微任务| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1 |8.执行微任务(即将微任务回调加入JS执行栈),打印‘script end’’后调用Promise.then,。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then || JS stack(JS执行栈) | Promise callback || Log | script start ,promise0,script end,promise1 |9.Promise.then : 微任务, push到微任务队列。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise1.then,Promise2.then || JS stack(JS执行栈) | Promise callback || Log | script start ,promise0,script end,promise1 |10.Promise1.then执行完成,JS执行栈pop, 微任务队列pop。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) | Promise2.then || JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1 |11.微任务队列内还有值继续执行,执行和Promise1.then一样,打印‘promise2’后发现没有后续;则Promise2.then执行完成,JS执行栈pop, 微任务队列pop。| 名称 | 值 || — | — || Tasks(宏任务) | script,setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1,promise2 |12.script执行完成,由它而引发的微任务执也行完成,此时可以认为宏任务script执行完成,宏任务队列pop。| 名称 | 值 || — | — || Tasks(宏任务) | setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1,promise2 |13.执行下一个宏任务,JS执行栈push,打印‘setTimeout’| 名称 | 值 || — | — || Tasks(宏任务) | setTimeout callback || Microtasks(微任务) ||| JS stack(JS执行栈) | setTimeout callback || Log | script start ,promise0,script end,promise1,promise2,setTimeout |14.宏任务setTimeout执行完成,JS执行栈pop,宏任务队列pop| 名称 | 值 || — | — || Tasks(宏任务) ||| Microtasks(微任务) ||| JS stack(JS执行栈) ||| Log | script start ,promise0,script end,promise1,promise2,setTimeout |### 异步编程的几种方案
假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。
jQuery.subscribe("done", f2); function f1(){ setTimeout(function () { // f1的任务代码 jQuery.publish("done") ; // f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。 }, 1000); } jQuery.unsubscribe("done", f2); // f2完成执行后,也可以取消订阅(unsubscribe)
优点:将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,
Promise
对象提供统一的接口,使得控制异步操作更加容易。缺点:
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。- 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。- 当处于
Pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); // Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。 promise.then(function(value) { // success }, function(error) { // failure });
async
函数完全可以看作多个异步操作,包装成的一个Promise
对象,而await
命令就是内部then
命令的语法糖。async
函数返回一个Promise
对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50); ```**最后一句**学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。* [完全熟练掌握 eventLoop](https://link.juejin.cn/?target=https%3A%2F%2Fjakearchibald.com%2F2015%2Ftasks-microtasks-queues-and-schedules%2F "https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/")
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享