JavaScript是浏览器脚本语言,主要用途是与用户互动以及操作DOM,所以它是单线程的,也就是说同一个时间只能做一件事。
几个概念先了解一下:
主线程:先进先出,script标签里面包含的内容,或者是直接访问某一个js文件时,里面的可以在当前作用域直接执行的所有内容(执行的方法,new出来的对象等);
执行栈:先入后出,后入先出,用于存储在代码执行期间创建的所有执行上下文
任务队列:所有任务需要排队,前一个任务结束,才会执行后一个任务;
任务又分为同步任务和异步任务
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务:不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行;
异步任务又分为宏任务(macro-task)和微任务(micro-task)
宏任务包括:setTimeout、setInterval
微任务包括:promise.then、process.nextTick,async/await
注意:
- 同一次事件循环中,微任务永远比宏任务先执行;
- new Promise() 是同步任务,resolve、.then、.catch等方法才是异步任务;
先执行主线程同步任务,遇到异步宏任务将异步宏任务放入宏任务队列,遇到异步微任务放到微任务队列,所有同步代码执行完毕后再将异步微任务调入主线程执行,执行完毕再将宏任务队列中的宏任务依次调入主线程执行。一直这样循环,反复执行,就是事件循环机制。
示例一:
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
resolve()
}).then(res => {
console.log(3)
})
console.log(4)
解析:
-执行同步代码,输出1
-new Promise
内部属于同步输出2
-.then
推入微任务队列,之后执行代码输出4
-整个script代码块属于宏任务此时执行栈为空检查微任务队列,执行微任务输出3
输出: 1 2 4 3
示例二:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
}).then(() => {
console.log('then1')
}).then(() => {
console.log('then2')
})
console.log('end')
解析:
-执行同步代码输出'start'
-遇到setTimeout
放入宏任务队列,继续执行
-new Promise
内部属于同步,输出promise
-之后的两个.then
放入微任务队列,继续执行
-输出'end'
-执行栈为空 (同一次事件循环中,微任务永远在宏任务之前)
-检查微任务队列,输出'then1'
,'then2'
-清空微任务队列后执行下一个宏任务
-输出setTimeout
输出:start promise end then1 then2 setTimeout
示例三:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
new Promise((resolve) => {
console.log('promise2')
resolve()
}).then( () => {
console.log('then3')
})
}, 0)
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('then1')
}).then(() => {
console.log('then2')
})
console.log('end')
解析:
-执行同步代码,输出'start'
-遇到setTimeout
放入宏任务队列,继续向下执行
-new Promise
内部属于同步,输出promise1
-之后的两个.then
放入微任务队列,继续执行
-输出'end'
-检查微任务队列,输出'then1'
,'then2'
-清空微任务队列后执行下一个宏任务
-输出setTimeout
-执行new Promise
内部代码,输出promise2
-.then
放入微任务队列
-执行栈为空,检查微任务队列,输出then3
(注意:此时的微任务队列是新创建)
输出: start promise1 end then1 then2 setTimeout promise2 then3
示例四:
console.log('start')
setTimeout(() => {
console.log('setTimeout1')
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then( () => {
console.log('then1')
})
}, 0)
new Promise((resolve) => {
console.log('promise2')
resolve()
}).then(() => {
console.log('then2')
}).then(() => {
console.log('then3')
})
new Promise((resolve) => {
console.log('promise3')
resolve()
}).then(() => {
console.log('then4')
}).then(() => {
console.log('then5')
})
setTimeout(() => {
console.log('setTimeout2')
new Promise((resolve) => {
console.log('promise4')
resolve()
}).then( () => {
console.log('then6')
})
}, 0)
console.log('end')
解析:
-执行同步代码,输出'start'
-遇到setTimeout
放入宏任务队列,继续向下执行
-new Promise
内部属于同步,输出promise2
,继续执行
-之后的两个.then
和下一个promise的.then
交替放入微任务队列
-new Promise
内部属于同步,输出promise3
-遇到setTimeout
放入宏任务队列,继续向下执行 (注意:此处两个setTimeout被放入同一个宏任务队列)
-输出'end'
-检查微任务队列,输出then2 then4 then3 then5
-执行下一个宏任务(取出第一个setTimeout
的回调执行)
-输出setTimeout1
-执行new Promise
内部代码,输出promise1
-.then
放入微任务队列
-执行栈为空,检查微任务队列,输出then1
(新创建的微任务队列)
-执行栈为空,执行宏任务队列的下一个宏任务
-输出setTimeout2
-执行new Promise
内部代码,输出promise4
-.then
放入微任务队列
-执行栈为空,检查微任务队列,输出then6
(新创建的微任务队列)
注意:
该案列中有两个Promise,并且每个Proimise有两个.then。当含有多个Promise时,他们的then操作会交替执行,这是由于推入微任务队列的时机不同产生了这种现象
输出: start promise2 promise3 end then2 then4 then3 then5 setTimeout1 promise1 then1 setTimeout2 promise4 then6