event loop 较难理解,面试中经常出现考察event loop执行顺序的问题,本文详细讲解event loop。
Event loop定义
1.这是 w3c 的定义
2.这是mdn的定义
3.这是node.js的定义
例题1:
// 回答输出顺序
setTimeout(() => {
console.log('setTimeout异步')
}, 0)
console.log('同步')
答案:
同步
setTimeout异步
回答错误的同学建议先复习一下异步JavaScript
例题2:
// 回答输出顺序
setTimeout(() => {
console.log('setTimeout异步')
}, 0)
Promise.resolve().then(() =>{
console.log('promise异步')
})
console.log('同步')
答案:
同步
promise异步
setTimeout异步
代码promise异步任务代码写在setTimeout异步任务代码之后,执行结果却是promise异步任务先执行setTimeout异步任务后执行,这是由event loop控制的,event loop控制异步任务回调的执行顺序。
task、microtask定义
event loop把异步任务分为task(:也有说法是macrotask,不过我没有在w3c和mdn中找到macrotask的说法和定义,下文统一用task)和microtask.
task
大多数异步操作都是task,例如:Event事件(click,mousemove等监听事件),fetch(网络请求),Parsing(浏览器对html的解析),Dom操作(dom的增删等),定时器(setTimeout/setInterval)等(:记住microtask就可以,除了microtask就是task,microtask好记)。
浏览器执行代码的时候遇到task会将task推入到一个task queue队列中,等同步代码都执行完,浏览器会在合适的时机按顺序依次执行task quque内的内容,直到清空task queue队列。
task queue队列执行中及清空后,新产生的task推入一个新的task queue中,等待浏览器的下次执行(:这段不是很严谨,不过代码的执行顺序是高度等效的,严谨的解释请参考w3c)。
// 伪代码近似表示
const taskQueue = [] // task queue队列
// 遇到task任务就加到队列里
taskQueue.push(() => {console.log('task任务1')})
taskQueue.push(() => {console.log('task任务2')})
taskQueue.push(() => {console.log('task任务3')})
while(taskQueue.length) {
// 按顺序一个一个执行
taskQueue.shift()()
}
例题:
// 回答输出顺序
console.log('同步1') // 同步任务直接执行
setTimeout(() => {
console.log('setTimeout异步1') // 丢到 task queue里的第1个task
}, 0)
setTimeout(() => {
console.log('setTimeout异步2') // 丢到 task queue里的第2个task
}, 0)
setTimeout(() => {
console.log('setTimeout异步3') // 丢到 task queue里的第3个task
}, 0)
console.log('同步2') // 同步任务直接执行
// 现在执行task queue!
答案:
// 先执行同步任务
同步1
同步2
// 后依次执行task queue任务
setTimeout异步1
setTimeout异步2
setTimeout异步3
microtask
w3c:
mdn:
单纯的microtask执行在表现上和task区别不大,microtask 任务推入microtask queue,执行完同步代码后,在依次执行microtask queue内的回调,但是microtask queue 执行过程中产生的新microtask 会直接推入当前microtask queue最后被执行,不需要像task queue一样等待浏览器下次执行。
例题:
Promise.resolve().then(() => {
console.log('promise1')
})
Promise.resolve().then(() => {
console.log('promise2')
}).then(() => {
console.log('promise4')
})
Promise.resolve().then(() => {
console.log('promise3')
})
console.log('同步1')
答案:
同步1
promise1
promise2
promise3
promise4
解析:
// 初始microtask queue 为空[]
/*
微任务:() => {
console.log('promise1')
}入队,队列[
() => {
console.log('promise1')
}
] */
Promise.resolve().then(() => {
console.log('promise1')
})
/*
微任务:(() => {
console.log('promise2')
}).then(() => {
console.log('promise4')
})入队,队列[
() => {
console.log('promise1')
},
(() => {
console.log('promise2')
}).then(() => {
console.log('promise4')
})
] */
Promise.resolve().then(() => {
console.log('promise2')
}).then(() => {
console.log('promise4')
})
/*
微任务:() => {
console.log('promise3')
}入队,队列[
() => {
console.log('promise1')
},
(() => {
console.log('promise2')
}).then(() => {
console.log('promise4')
}),
() => {
console.log('promise3')
}
] */
Promise.resolve().then(() => {
console.log('promise3')
})
// 执行 console.log('同步1')
console.log('同步1')
// 执行 microtask queue
/*
执行 console.log('promise1'),队列 [
(() => {
console.log('promise2')
}).then(() => {
console.log('promise4')
}),
() => {
console.log('promise3')
}
] */
/*
执行 console.log('promise2'),
微任务:() => {
console.log('promise4')
}入队,队列 [
() => {
cnsole.log('promise3')
},
() => {
console.log('promise4')
}
] */
/*
执行 console.log('promise3'),队列 [
() => {
console.log('promise4')
}
] */
/*
执行 console.log('promise4'),队列 [], 微任务执行完毕 */
task vs micotask
mdn:
看起来云里雾里,只需要记住一句:在task queue中每个task执行之前,都会先执行microtask queue 中的microtask,因为task的执行,可能会向microtask queue中添加新的microtask.
例题:
setTimeout(() => {
console.log('setTimeout1')
}, 0)
setTimeout(() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
}, 0)
setTimeout(() => {
console.log('setTimeou3')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
})
console.log('同步1')
答案:
同步1
promise1
setTimeout1
setTimeout2
promise2
setTimeou3
解析:
// 初始宏任务队列 []
// 初始微任务队列 []
/*
宏任务:() => {
console.log('setTimeout1')
}入队宏任务队列,此时
宏任务队列:[
() => {
console.log('setTimeout1')
}]
微任务队列: []
*/
setTimeout(() => {
console.log('setTimeout1')
}, 0)
/*
宏任务:() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
}入队宏任务队列,此时
宏任务队列:[
() => {
console.log('setTimeout1')
},
() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
}]
微任务队列: []
*/
setTimeout(() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
}, 0)
/*
宏任务:() => {
console.log('setTimeout3')
}入队宏任务队列,此时
宏任务队列:[
() => {
console.log('setTimeout1')
},
() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
},
() => {
console.log('setTimeout3')
}]
微任务队列: []
*/
setTimeout(() => {
console.log('setTimeou3')
}, 0)
/*
微任务:() => {
console.log('promise1')
}入队宏任务队列,此时
宏任务队列:[
() => {
console.log('setTimeout1')
},
() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
},
() => {
console.log('setTimeout3')
}]
微任务队列: [
() => {
console.log('promise1')
}
]
*/
Promise.resolve().then(() => {
console.log('promise1')
})
// 执行同步代码 console.log('同步1')
console.log('同步1')
/*
此时队列情况:
宏任务队列:[
() => {
console.log('setTimeout1')
},
() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
},
() => {
console.log('setTimeout3')
}]
微任务队列: [
() => {
console.log('promise1')
}
]
微任务优先级更高,执行宏任务前先执行微任务队列
执行微任务:() => {
console.log('promise1')
}
微任务队列: []
宏任务队列:[
() => {
console.log('setTimeout1')
},
() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
},
() => {
console.log('setTimeout3')
}]
*/
/*
微任务队列执行完毕,开始执行宏任务
执行宏任务:() => {
console.log('setTimeout1')
}
微任务队列: []
宏任务队列:[
() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('promise2')
})
},
() => {
console.log('setTimeout3')
}]
*/
/*
微任务优先级更高,执行宏任务前先执行微任务队列
微任务队列执行完毕(为空),开始执行宏任务
执行宏任务:() => {
console.log('setTimeout2')
// 这里产生了微任务,() => {
// console.log('promise2')
//}入队微任务队列
Promise.resolve().then(() => {
console.log('promise2')
})
},
微任务队列: [
() => {
console.log('promise2')
}
]
宏任务队列:[
() => {
console.log('setTimeout3')
}]
*/
/*
微任务优先级更高,执行宏任务前先执行微任务队列
执行微任务:() => {
console.log('promise2')
}
微任务队列: []
宏任务队列:[
() => {
console.log('setTimeout3')
}]
*/
/*
微任务队列执行完毕,开始执行宏任务
执行宏任务:() => {
console.log('setTimeout3')
},
微任务队列: []
宏任务队列:[]
*/
// 全部执行完成
微任务和宏任务的执行都用可能产生新的微任务和宏任务,因此先分别画出微任务和宏任务队列,然后根据执行入队和出队,否则很容易出错。
习题
promise习题
promise要注意区分哪些是同步代码,哪些是异步代码
例题:
new Promise((resolve) => {
console.log('这是promise的同步1')
resolve()
console.log('这是promise的同步2')
}).then(() =>{
console.log('这是promise的异步1')
})
console.log('同步')
答案:
这是promise的同步1
这是promise的同步2
同步
这是promise的异步1
解析:
// 不要看到promise就当异步处理,then里面才是异步,new Promise 声明是同步!
new Promise((resolve) => {
// 这里是同步!
console.log('这是promise的同步1')
// 这里相当于then才是异步
resolve()
// 这里是同步!
console.log('这是promise的同步2')
}).then(() =>{
console.log('这是promise的异步1')
})
// 同步,但是在promise声明的后面!
console.log('同步')
async习题
async函数中的await左下部分相当于new Promise().then
/*
async () => {
xxx代码
这上面也是右上
----------------|
这里开始是左下 | 这里是右上
const p = | await xxx();
|-------------------
这里也是左下
xxx代码
}
*/
例题1:
const asyncFunction = async () => {
console.log('async同步1')
await new Promise((resolve) => {
console.log('这是promise的同步1')
resolve()
console.log('这是promise的同步2')
})
console.log('async异步1')
}
asyncFunction()
console.log('同步1')
答案:
async同步1
这是promise的同步1
这是promise的同步2
同步1
async异步1
解析:
// async函数 第一个await 前面是同步,之后的相当于new Promise.then()是微任务
const asyncFunction = () => {
console.log('async同步1')
new Promise((resolve) => {
console.log('这是promise的同步1')
resolve()
console.log('这是promise的同步2')
}).then(() => {
console.log('async异步1')
})
}
asyncFunction()
console.log('同步1')
例题2:
const asyncFunction = async () => {
console.log('async同步1')
await new Promise((resolve) => {
console.log('这是promise的同步1')
resolve()
console.log('这是promise的同步2')
})
console.log('async异步1')
await new Promise((resolve) => {
resolve()
})
console.log('async异步2')
}
Promise.resolve().then(() => {
console.log('promise异步1')
})
asyncFunction()
Promise.resolve().then(() => {
console.log('promise异步2')
})
console.log('同步1')
答案:
async同步1
这是promise的同步1
这是promise的同步2
同步1
promise异步1
async异步1
promise异步2
async异步2
解析:
// 只要把async函数内的await都换成promise就可以了
const asyncFunction = () => {
console.log('async同步1')
new Promise((resolve) => {
console.log('这是promise的同步1')
resolve()
console.log('这是promise的同步2')
// 第一个await 变成.then
}).then(() => {
console.log('async异步1')
return new Promise((resolve) => {
resolve()
})
// 第二个await也变成.then
}).then(() => {
console.log('async异步2')
})
}
Promise.resolve().then(() => {
console.log('promise异步1')
})
asyncFunction()
Promise.resolve().then(() => {
console.log('promise异步2')
})
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
解析:
// 1.先把async函数都改写成promise形式
function async1(){
// 同步
console.log('async1 start')
new Promise((resolve) => {
// resolve中的函数同步执行
resolve(async2())
// then入队微任务
}).then(() => {
console.log('async1 end')
})
}
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')
总结
event loop 问题尤其是涉及到async的情况下非常复杂,因为篇幅限制,本文无法列举全部内容,需要读者多尝试总结,另外附上本文没有说明的event loop其他情况,读者查看对应连接即可:
1.为什么需要微任务
2.node环境下的setImmediate、process.nextTick
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!