1、JS运行机制
JS最大的特点就是单线程,所以他同一时间只能做一件事情。使单线程不阻塞,就是事件循环。
在JS当中分为两种任务:
- 同步任务:立即执行的任务,一般放在主线程中(主执行栈)。
- 异步任务:异步执行的任务,进入任务队列(task queue)。只有任务队列通知主线程,某个异步任务可以执行力,该任务才会进入主线程执行。
事件循环就是不断重复上面两步骤、异步与同步任务执行如下。
2、宏任务与微任务
异步任务又分为宏任务和微任务。
常见的微任务有:
- Promise.then
- Async/Await
- MutationOberver
- Object.oberver
- process.nextTick
常见的宏任务有:
- script(可以理解其为外层同步代码)
- setTimeout/setInterval
- UI
- postMessage\MessageChannel
- I/O
- setImmediate
他的运行机制是:
- 执行一个宏任务、如果遇到微任务就将他放到微任务的队列中
- 执行当前宏任务后,检查微任务事件队列,将其执行完毕
- 循环上面的步骤如下图
3、Async/Await
async是异步的意思,await可以裂解为async wait。所以可以理解为:
- async:声明一个异步的方法
- await:等待异步方法执行
Async/Await相对于Promise的优势:
- Promise虽然拜托了地狱回调,但是他很的链式调用也会带来阅读负担
- Async/Await是异步函数但是几乎同步的写法,非常优雅
- Async/Await错误处理友好,可以使用成熟的try/catch
- 调试友好,Promise由于没有代码块,不能反回表达式的箭头函数中设置断点
- async ,awit命令后面返回Promise对象。如果不是对象直接返回对应的值。
function f() {
return Promise.resolve('TEST');
}
// asyncF is equivalent to f!
async function asyncF() {
return 'TEST';
}
- 不管await后面跟着是什么,await都会阻塞后面的代码
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
//1,fn2,3,2
上面例子中,await会阻塞下面的代码(即加入微任务队列),先执行async外面的同步代码,执行完毕之后回到async函数执行之前阻塞的代码。
4、流程分析
下面一个案例可以看到时间循环机制为了达到单线程不阻塞,他的执行流程是怎么样的。
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')
//script start,async1 start,async2,promise1,script end,async1 end,promise2,settimeout
分析:
- 执行同步任务async1、promise、console直接打印
script start
、async1 start
、async2
、promise1
、script end
- 遇到await、Promise.then添加到微任务队列
- 遇到settimeout添加到宏任务队列
- 清空微任务队列打印
async1 end
、promise2
处理宏任务队列打印
settimeout
参考文献:
javaScript运行机制 (同步与异步)的理解_js同步与异步的机制-CSDN博客
面试官:说说你对事件循环的理解 | web前端面试 - 面试官系列