JS 的事件循环 event loop
事件循环⼜叫做消息循环,是浏览器渲染主线程的⼯作⽅式。 在 Chrome 的源码中,它开启⼀个不会结束的 for 循环,每次循环从消息 队列中取出第⼀个任务执⾏,⽽其他线程只需要在合适的时候将任务加⼊到 队列末尾即可。 过去把消息队列简单分为宏队列和微队列,这种说法⽬前已⽆法满⾜复杂的 浏览器环境,取⽽代之的是⼀种更加灵活多变的处理⽅式。 根据 W3C 官⽅的解释,每个任务有不同的类型,同类型的任务必须在同⼀ 个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级, 在⼀次事件循环中,由浏览器⾃⾏决定取哪⼀个队列的任务。但浏览器必须 有⼀个微队列,微队列的任务⼀定具有最⾼的优先级,必须优先调度执⾏。
———————————————— 版权声明:本文为CSDN博主「JackieDYH」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/JackieDYH/a…
单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,串行执行任务;异步执行就是cpu跳过等待,先处理后续的任务(CPU与网络模块、timer等并行进行任务)。由此产生了任务队列与事件循环,来协调主线程与异步模块之间的工作。
首先把JS执行代码操作 分为主线程,任务队列,任何一段js代码的执行都可以分为以下几个步骤:
- 步骤一: 主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
- 步骤二: 当主线程遇到异步操作的时候,将异步操作交给对应的API进行处理;
- 步骤三: 当异步操作处理完成,推入任务队列中
- 步骤四: 主线程执行完毕后,查询任务队列,取出一个任务,并推入主线程进行处理
- 步骤五: 重复步骤二、三、四
其中常见的异步操作有:ajax请求,setTimeout,还有类似onclik事件等
任务队列:
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列
任务队列 的类型:
任务队列分为 宏任务(macrotask queue) 和 微任务(microtask queue)
宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务主要包含:Promise、MutationObserver、process.nextTick(Node.js 环境)
console.log('1, time = ' + new Date().toString()) // 1.进入主线程,执行同步任务,输出1
setTimeout(macroCallback, 0)// 2. 加入宏任务队列 // 7.开始执行此定时器宏任务,调用macroCallback,输出4
new Promise(function (resolve, reject) {//3.加入微任务队列
console.log('2, time = ' + new Date().toString())//4.执行此微任务中的同步代码,输出2
resolve()
console.log('3, time = ' + new Date().toString())//5.输出3
}).then(microCallback)// 6.执行then微任务,调用microCallback,输出5
//函数定义
function macroCallback() {
console.log('4, time = ' + new Date().toString())
}
function microCallback() {
console.log('5, time = ' + new Date().toString())
}
再来分析一个简单的例子:
console.log('0');
setTimeout(() => {
console.log('1');
new Promise(function(resolve) {
console.log('2');
resolve();
}).then(()=>{
console.log('3');
})
new Promise(resolve => {
console.log('4');
for(let i=0;i<9;i++){
i == 7 && resolve();
}
console.log('5');
}).then(() => {
console.log('6');
})
})
- 进入主线程,检测到log为普通函数,压入执行栈,输出0;
- 检测到setTimeOut是特殊的异步方法,交给其他模块处理,其回调函数加入 宏任务(macrotask)队列;
- 此时主线程中已经没有任务,开始从任务队列中取;
- 发现微任务队列为空,则取出宏任务队列首项,也就是刚才的定时器的回调函数;
- 执行其中的同步任务,输出1;
- 检测到promise及其resolve方法是一般的方法,压入执行栈,输出2,状态改变为resolve;
- 检测到这个promise的then方法是异步方法,将其回调函数加入 微任务队列;
- 紧接着又检测到一个promise,执行其中的同步任务,输出4,5,状态改变为resolve;
- 然后将它的then异步方法加入微任务队列;
- 执行微任务队列首项,也就是第一个promise的then,输出3;
- 再取出微任务队列首项,也就是第二个promise的then,输出6;
- 此时主线程和任务队列都为空,执行完毕;