单线程
JavaScript 是一门单线程语言。同一时间只能做一件事,如果出现了某个耗时任务,就可能出现主线程的阻塞,为了防止这种情况,JavaScript就通过同步和异步来处理
调用栈(Call Stack)
调用栈是解释器追踪函数执行流的一种机。
- 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
- 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
- 当分配的调用栈空间被占满时,会引发“堆栈溢出”错误。
function greeting() {
sayHi();
}
function sayHi() {
return "Hi!";
}
greeting();
执行流程:
- 调用函数
greeting() - 将
greeting()添加进调用栈 - 执行
greeting()函数体内的代码 - 调用函数
sayHi() - 将
sayHi()添加进调用栈 - 执行
sayHi()函数体内的代码 - 执行
greeting()函数体中sayHi()后面的代码 - 删除调用栈中的
sayHi()函数 - 执行完
greeting()函数体中的代码,返回到greeting()函数调用处,继续执行后面代码 - 删除调用栈中的
greeting()函数
任务队列(Callback Queue)
任务队列是一个事件队列,是一个先进先出的数据结构,排在前面的事件,将先被主线程读取。任务队列中的事件包括用户产生的事件(比如鼠标点击、页面滚动)网络请求、页面渲染等事件。
WebAPIs会将所有事件中已完成事件的回调推到对应的任务队列中
宏任务(Task)
所有在调用栈执行的代码都是宏任务
script, setTimeout, setInterval, setImmediate(node环境下是,浏览器环境不是), requestAnimationFrame(浏览器环境是,node则不是), I/O, UI rendering
微任务(Microtask)
在当前宏任务执行结束后,下个宏任务执行前,执行的就是微任务.
process.nextTick(node环境下是,而浏览器环境下不是), Promise, Object.observe,MutationObserver(在浏览器环境是,而node环境不是)
Event Loop
事件循环流程:
- 从宏任务队列中,按照入队顺序,找到第一个执行的宏任务,放入调用栈,开始执行
- 执行完宏任务下所有同步任务后,即调用栈清空后,该宏任务被推出宏任务队列,然后微任务队列开始按照入队顺序,依次执行其中的微任务,直至微任务队列清空为止,一次事件循环就结束了
- 接着从宏任务队列中,找到下一个执行的宏任务,开始第二个事件循环,直至宏任务队列清空为止
function fn(){
console.log(3);
}
setTimeout(()=>{
console.log(6);
},1000)
console.log(1);
new Promise(function(resolve, reject) {
console.log(2);
resolve()
}).then(function() {
console.log(5);
})
fn()
console.log(4);
setTimeout()函数添加到调用栈,将回调函数添加到宏任务队列,然后该函数被移除调用栈console.log(1)添加到调用栈后执行,并移除调用栈new Promise()添加到调用栈执行- 执行
console.log(2)和resolve() - 将
new Promise().then()推入微任务队列 new Promise()移除调用栈fn()函数添加到调用栈执行console.log(3)添加到调用栈后执行,并移除调用栈console.log(4)添加到调用栈后执行,并移除调用栈- 此时调用栈已经清空了,开始执行微任务队列,
new Promise().then()添加到调用栈 console.log(5)添加到调用栈后执行,并移除调用栈new Promise().then()移除调用栈- 此时微任务队列已经清空了,开始执行下一个宏任务,
setTimeout callback添加到调用栈 console.log(6)添加到调用栈后执行,并移除调用栈setTimeout callback移除调用栈
async/await
async function f1(){
await f2()
console.log(1);
}
async function f2(){
console.log(2);
}
f1()
// 2 1
await 前面的代码是同步代码,调用函数时会直接执行。
await f2()就是将f2()包装成Promise,跳出f1()函数执行,等同于:
async function f1(){
Promise.resolve(f2()).then(()=>{
console.log(1);
})
}
练习题
题一
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')
}, 0)
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
题二
async function async1() {
console.log(1)
await async2()
console.log(2)
}
async function async2() {
console.log(3)
setTimeout(function () {
console.log(4)
})
}
console.log(5)
setTimeout(function () {
console.log(6)
})
async1()
new Promise(function (resolve, reject) {
console.log(7)
setTimeout(function () {
console.log(8)
})
resolve()
}).then(function () {
console.log(13)
return new Promise(function (resolve, reject) {
console.log(9)
setTimeout(function () {
console.log(10)
})
resolve()
}).then(() => {
console.log(11)
setTimeout(function () {
console.log(12)
})
})
})
console.log(14)
// 5 1 3 7 14 2 13 9 11 6 4 8 10 12