第一点:JavaScript是一门单线程的语言,意味着同一时间内只能做一件事情,但是并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
第二点:所有的任务都分为同步任务、异步任务
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:ajax网络请求、setTimeout定时器函数
同步任务和异步任务流程图如下:
从上面的图中可以看到:
同步任务进⼊主线程,即主执⾏栈,异步任务进⼊任务队列,主线程内的任务执 ⾏完毕为空,会去任务队列读取对应的任务,推⼊主线程执⾏。那么上述过程的不断重复就称为事件循环
首先我们来说一下微任务和宏任务
首先来看一段代码:
console.log(1) setTimeout(()=>{ console.log(2) }, 0) new Promise((resolve, reject)=>{ console.log('new Promise') resolve() }).then(()=>{ console.log('then') }) console.log(3) 按照我们上面的流程图那么我们分析的: console.log(1):是一个同步任务,放在主线程中进行执行 setTimeOut():属于异步任务。放到Event Table,经过0秒后控制台输出2,回调推入Event Queue中 new Promise属于同步任务,放在主线程中直接执行,一旦创建就会执行 .then:属于异步任务,放在主线程中进行执行 那么分析的结果是:1 newPrimise 3 2 then 但是实际上的结果是:1 newPromise 3 then 2 那么我们分析的和正确的答案出现的不同的是在于:异步任务的执行顺序 那么队列是“先进先出”的数据结构,排在前面的事件会优先被主线程读取 我们举的例子是setTimeout回调事件是先进的队列,按道理来说应该是.then先执行, 但结果是setTimeout先执行, 最关键的原因是因为异步任务还分为微任务和宏任务
微任务:
⼀个需要异步执⾏的函数,执⾏时机是在主函数执⾏结束之后、当前宏任务结束之前
常⻅的微任务有:
1.Promise.then
2.MutaionObserver
3.Object.observe(已废弃;Proxy 对象替代)
4.process.nextTick(Node.js)
宏任务:
宏任务的时间粒度⽐较⼤,执⾏的时间间隔是不能精确控制的,对⼀些⾼实时性的需求就不太符合
常⻅的宏任务有:
1.script (可以理解为外层同步代码)
2.setTimeout/setInterval
3.UI rendering/UI事件
4.postMessage、MessageChannel
5.setImmediate、I/O(Node.js)
按照上图的流程它的执行机制是:
执行一个宏任务,如果遇到微任务就将他放到微任务的事件队列中,当前宏任务执行完之后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完毕
所以我们上面的那个执行结果是:
遇到 console.log(1) ,直接打印 1
遇到定时器,属于新的宏任务,留着后⾯执⾏
遇到 new Promise,这个是直接执⾏的,打印 'new Promise'
.then 属于微任务,放⼊微任务队列,后⾯再执⾏
遇到 console.log(3) 直接打印 3
好了本轮宏任务执⾏完毕,现在去微任务列表查看是否有微任务,发现 .then 的回调,执⾏它,打印 'then'
当⼀次宏任务执⾏完,再去执⾏新的宏任务,这⾥就剩⼀个定时器的宏任务了,执⾏它,打印 2
那么下面来说一下async以及await
async 就是⽤来声明⼀ 个异步⽅法,⽽ await 是⽤来等待异步⽅法执⾏
async用法:
function f() { return Promise.resolve('TEST'); } // asyncF is equivalent to f! async function asyncF() { return 'TEST'; }
await用法: await 命令后⾯是⼀个 Promise 对象,返回该对象的结果。如果不是 Promise 对 象,就直接返回对应的值
async function f(){ // 等同于 // return 123 return await 123 } f().then(v => console.log(v)) // 123
不管 await 后⾯跟着的是什么, await 都会阻塞后⾯的代码
async function fn1 (){ console.log(1) await fn2() console.log(2) // 阻塞 } async function fn2 (){ console.log('fn2') } fn1() console.log(3)
上⾯的例⼦中, await 会同步代码执⾏完,再回到 async 函数中,再执⾏之前阻塞的代码阻塞下⾯的代码(即加⼊微任务队列),先执⾏ async 外⾯的同步代码,
所以上述输出结果为: 1 , fn2 , 3 , 2
那么我们来上一个比较难的挑战吧:
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') }) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end') 分析: 1. 执⾏整段代码,遇到 console.log('script start') 直接打印结果,输出 script start 2. 遇到定时器了,它是宏任务,先放着不执⾏ 3. 遇到 async1() ,执⾏ async1 函数,先打印 async1 start ,下⾯遇到 await 怎么办? 先执⾏ async2 ,打印 async2 ,然后阻塞下⾯代码(即加⼊微任务列表),跳出去执⾏同步代码 4. 跳到 new Promise 这⾥,直接执⾏,打印 promise1 ,下⾯遇到 .then() ,它是微任务, 放到微任务列表等待执行 5. 最后⼀⾏直接打印 script end ,现在同步代码执⾏完了,开始执⾏微任务,即 await 下⾯的 代码,打印 async1 end 6.继续执⾏下⼀个微任务,即执⾏ then 的回调,打印 promise2 7. 上⼀个宏任务所有事都做完了,开始下⼀个宏任务,就是定时器,打印 settimeout 所以最后的结果是: scrit start 、 async1 start 、 async2 、 promise1 、 script end 、 async1 end 、 promise2 、 settimeout

