javaScript的执行机制(Event-Loop)
详细内容请关注我的博客地址
理解js的执行机制是一件至关重要的事情
var a = 10;
var b = 20;
console.log(a);
console.log(b);
//输出: 10 20
当我们接触到这样的代码,我们心情舒畅,因为一眼就可以看出我们应该先执行那个步骤,再执行哪个步骤,随后我们根据需求添加了定时器
var a = 10;
var b = 20;
setTimeout(() => {
console.log(a);
}, 1000);
console.log(b);
// 20 10
我们遇到了小小的麻烦,执行过程需要考虑定时器的使用,这还算可以接受,但是随着需求增加我们使用定时器
,async
,promise
,await
等等异步操作的次数也会越来越多,我们会觉得执行更加复杂,甚至心态爆炸,因此我们必须理解js的执行机制,才能有效解决这个难以处理的问题
javaScript的事件循环
我们总会听到js是一门单线程语言,虽然随着语言的发展,技术大牛们也在探索多线程的发展,但是至今为止所有的类似多线程都是用单线程模拟出来的
由于Js是单线程,所以正常情况下代码是顺序执行的,但是很多时候有些代码不需要直接执行,因此我们把javascript中单线程任务分为以下两类:
- 同步任务
- 异步任务
任务队列
任务队列task Queue
,即队列,是先进先出的数据结构
MacroTask(宏任务)
- js的全部代码,
setimeout
、setInterval
、setImmediate
、I/O
、UI Rendering
MicroTask(微任务)
Process.nextTick()
、Promise
、Object.observe(废弃)
、MutationObserver
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOF74brR-1592040777428)(https://user-gold-cdn.xitu.io/2017/11/21/15fdd88994142347?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)]
从以上导图可以看出:
- 同步与异步任务分别在不同场所执行,同步任务进去主线程,异步任务进入Event Table并为异步任务注册回调函数
- 任务完成后EventTable会将任务放入EventQueue
- 主线程内的任务执行完毕后任务队列此时为空,任务队列会读取Event Queue中的任务加入队列,进入主线程执行
- 以上过程不断重复,即为事件循环(Event Loop)
js引擎中存在monitoring process进程,它会不断地对主线程任务栈进行检查,一旦任务栈为空,则会去Event Queue检查是否有等待调用的函数
Event Table中的执行过程
执行栈在执行玩同步任务后,查看执行栈是否为空,如果执行栈为空,则查微任务(microTask
)栈是否为空,如果不为空,则执行完成所有的微任务,如果为空则执行宏任务(Tasks
)。
每一次宏任务执行完毕,都检查微任务队列是否为空,不为空则按照出对原则(先入先出)执行所有微任务,然后设置微任务队列为null
,然后执行宏任务,如此循环
简单示例
Ajax
ajax技术是最常用到的数据异步数据请求技术,作为异步请求,他的执行顺序完全符合上面的流程
let data = [];
$.ajax({
url: "www.sleepygod.xyz",
method: post,
data: data,
success: () => {
console.log("数据传输成功!")
}
})
console.log("代码执行结束")
上面的代码执行顺序就是:
- ajax为异步任务进入Event Table,注册回调函数success
- 顺序执行主线程任务打印
代码执行结束
- Event Table中ajax执行结束,success被放入Event Queue
- 主线程执行完毕,任务栈为空,主线程从Event Queue读取success函数放入主线程执行
- 此时打印
数据传输成功
setTimeout
setTimeout
是延时执行函数,同样属于异步操作,总见到有人将setTimeout的延时设置为0,其实根据HTML标准,无论怎么设置,最低标准等待时间都是4ms
console.log("start")
setTimeout(() => {
console.log("延时操作")
}, 3000)
console.log("end")
上面代码的执行过程是这样的:
- 打印
start
- 定时器为异步操作,放入Event Table, 注册回调函数
- 主任务栈中执行打印
end
- Event Table中定时器执行完毕,打印操作放入Event Queue
- 主任务栈为空,Event Table中的任务放入主线程执行,打印
延时操作
复杂示例
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
执行上述代码时,我们先将代码进行分类:
Tasks: run script、setTimeout callback //宏任务
MicroTask: Promise.then() //微任务
Js stack: script
log: script start, script end
执行同步代码: 打印script start
,script end
划分宏任务与微任务进行详细划分
Tasks: run script、setTimeout callback //宏任务
MicroTask: Promise1.then() //微任务
JsTask: Promise1
log: script start, script end,Promise1
宏任务执行完毕后,任务栈为空,查询微任务,发现Promise,执行Promise,打印promise1
,将.then()放入微任务
Tasks: run script、setTimeout callback //宏任务
MicroTask: //微任务
JsTask: Promise1.then()
log: script start, script end,Promise1, Promise2
继续执行微任务,打印Promise2
,微任务栈随即清空
Tasks: setTimeout callback //宏任务
MicroTask: //微任务
JsTask: setTimeout callback
log: script start, script end,Promise1, Promise2,setTimeout
继续执行宏任务,执行setTimeout callback打印setTimeout
,随即宏任务与微任务全部清空
async/await
javascript在底层已经将async/await
转换为promise
和then
回调函数
终极示例
这是一个摘自掘金作者作者:ssssyoki文章中的例子,我研究过后才真正理解了Event Loop的机制。
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
上述代码的执行顺序如下:
-
首先执行同步任务 :
Tasks: script, setTimeout1 callback, setTimeout2 callback //宏任务 MicroTasks: process.nextTick1, Promise.then()//微任务 JsTask: script log: 1,7
-
同步任务执行结束查询微任务栈,发现微任务,执行微任务:
Tasks: script, setTimeout1 callback, setTimeout2 callback //宏任务 MicroTasks: //微任务 JsTask: Promise.then() log: 1,7,6,8
-
微任务执行结束,执行宏任务:
Tasks: setTimeout1 callback, setTimeout2 callback //宏任务 MicroTasks: process.nextTick, Promise.then()//微任务 JsTask: setTimeout1 callback log: 1,7,6,8,2,4
-
宏任务执行结束,查询并执行微任务:
Tasks: setTimeout1 callback, setTimeout2 callback //宏任务 MicroTasks: //微任务 JsTask: Promise.then() log: 1,7,6,8,2,4,3,5
-
微任务执行结束,继续执行宏任务,发现setTimeout2:
Tasks: setTimeout2 callback //宏任务 MicroTasks: process.nextTick, Promise.then()//微任务 JsTask: setTimeout2 callback log: 1,7,6,8,2,4,3,5,9,11
-
宏任务执行结束,查询微任务,执行并清空微任务:
Tasks: setTimeout2 callback //宏任务 MicroTasks: //微任务 JsTask: log: 1,7,6,8,2,4,3,5,9,11,10,12
process.nextTick属于微任务又优先于所有微任务,有process.nextTick存在时应优先执行process.nextTick。
总结
javaScript时一门单线程的语言,js的执行机制为事件循环(Event Loop),理解了这一概念将会更有利于之后与js相关知识的学习。
javaScript在浏览器和node中的运行机制并不一致,以上的机制只适用于浏览器环境,并不适用于node环境-
MicroTasks: //微任务
JsTask:
log: 1,7,6,8,2,4,3,5,9,11,10,12
process.nextTick属于微任务又优先于所有微任务,有process.nextTick存在时应优先执行process.nextTick。
总结
javaScript时一门单线程的语言,js的执行机制为事件循环(Event Loop),理解了这一概念将会更有利于之后与js相关知识的学习。
javaScript在浏览器和node中的运行机制并不一致,以上的机制只适用于浏览器环境,并不适用于node环境-
详细内容请关注我的博客地址