众所周知,js是一门单线程的编程语言,在设计之初,它就注定了单线程的命运,好比当咱们处理dom时,若是有多个线程同时操做一个dom,那将很是混乱。html
既然是单线程,那么它必定有一套严谨的规则,来使代码可以乖乖的按开发者的设计运行,今天咱们就来研究其中的奥秘,了解一下js的event loop(事件循环)。node
聊js事件环,绕不开聊异步(在个人另外一篇文章拥抱并扒光Promise中对Promise这种异步解决方案有详细介绍)ajax
为何要异步?假设没有异步,咱们发送一个ajax请求,后端代码运行的很慢,这时浏览器会发生阻塞,若是十秒才响应,这十秒咱们该干吗?(或许能够看博尔特跑个百米)编程
虽然在网页诞生之初,确实有这样的状况,但现在这样的页面是会被用户骂娘的。因而异步的做用显露无遗,js开启一个异步线程,何时请求完成,何时执行回调函数,而这期间,其余代码也能够正常运行。后端
既然是单线程,就像一次只能过一我的的独木桥,人要排队,那么代码也要排队。这时,同步代码和异步代码的排队机制是不同的api
同步:在主线程(至关于独木桥上)上排队的任务,前一个任务执行完,下一个任务才能够执行,若是前一个任务没执行完,下一个任务要一直等待。就像过独木桥,前面的人不过去,你死等也得等,否则就5253B翻腾两周半入水。浏览器
异步:主线程先无论IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回告终果,再回过头,把挂起的任务继续执行下去。就像过独木桥,你惧怕不敢过,你就让后面的人先过,何时你敢了你再过。而你调整心态的过程,主线程不考虑。dom
主线程会不断的重复以上三步,这样就构成了事件环,用图表示异步
经过这张图,咱们能够知道,主线程运行时,产生堆和执行栈,栈中的代码会调用一些api,好比seTtimeou、click等,这些异步操做会讲他们的回调放入callback queue中,当执行栈中的代码运行完,主线程回去读取queue中的任务。编程语言
console.log(1)
setTimeout(function(){
console.log(2)
})
console.log(3)
复制代码
咱们都知道结果是1 3 2,结合上面咱们梳理一下这段代码的执行顺序
一、从上到下运行执行栈中的同步代码console.log(1)
二、看到setTimeout,把回调函数放入任务队列中去
三、执行console.log(3)
四、主线程上没有任务了,去任务队列中执行setTimeout的回调,console.log(2)
显然node要比浏览器复杂一些,它的流程是这样的:
Node还有一些不一样,它提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate。它们能够帮助咱们加深对"任务队列"的理解。
process.nextTick方法能够在当前"执行栈"的尾部,下一次Event Loop(主线程读取"任务队列")以前,触发回调函数。也就是说,它指定的任务老是发生在全部异步任务以前。
setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务老是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。
大概能够理解成process.nextTick有权插队
setTimeout(function(){
console.log(1)
})
process.nextTick(function () {
console.log(2);
process.nextTick(function (){
console.log(3)
});
});
setTimeout(function () {
console.log(4);
})
复制代码
虽然1在上面,但结果是2 3 1 4,就像咱们上面说的,process.nextTick会在主线程读取任务队列时插队
再看setImmediate
setImmediate(function () {
console.log(1);
setImmediate(function B(){
console.log(2)
})
})
setTimeout(function () {
console.log(3);
}, 0)
复制代码
结果多是312,也多是132
为何会出现上面有的先有的后的状况呢,难道除了人类社会代码世界也有特权么,是的,咱们将任务分为两种:
微任务Microtask,有特权,能够插队,包括原生Promise,Object.observe(已废弃), MutationObserver, MessageChannel;
宏任务Macrotask,没有特权,包括setTimeout, setInterval, setImmediate, I/O;
console.log("1");
setTimeout(()=>{
console.log(2)
Promise.resolve().then(()=>{
console.log(3);
process.nextTick(function foo() {
console.log(4);
});
})
})
Promise.resolve().then(()=>{
console.log(5);
setTimeout(()=>{
console.log(6)
})
Promise.resolve().then(()=>{
console.log(7);
})
})
process.nextTick(function foo() {
console.log(8);
process.nextTick(function foo() {
console.log(9);
});
});
console.log("10")
复制代码
执行顺序:
1,输出1
2,将setTimeout(2)push进宏任务
3,将then(5)push进微任务
4,在执行栈底部添加nextTick(8)
5,输出10
6,执行nextTick(8)
7,输出8
8,在执行栈底部添加nextTick(9)
9,输出9
10,执行微任务then(5)
11,输出5
12,将setTimeout(6)push进宏任务
13,将then(7)push进微任务
14,执行微任务then(7)
15,输出7
16,取出setTimeout(2)
17,输出2
18,将then(3)push进微任务
19,执行微任务then(3)
20,输出3
21,在执行栈底部添加nextTick(4)
22,输出4
23,取出setTimeout(6)
24,输出6