上次你们跟我吃饱喝足又撸了一遍PromiseA+,想必你们确定满脑子想的都是西瓜可乐......node
什么西瓜可乐!明明是Promise!面试
呃,清醒一下,今天你们搬个小板凳,听我说说JS中比较有意思的事件环,在了解事件环以前呢,咱们先来了解几个基本概念。api
栈是一种遵循后进先出(LIFO)的数据集合,新添加或待删除的元素都保存在栈的末尾,称做栈顶,另外一端称做栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底promise
感受提及来并非很好理解,咱们举个例子,好比有一个乒乓球盒,咱们不停的向球盒中放进乒乓球,那么最早放进去的乒乓球必定是在最下面,最后放进去的必定是在最上面,那么若是咱们想要把这些球取出来是否是就必须依次从上到下才能拿出来,这个模型就是后进先出,就是咱们后进入球盒的球反而最早出来。浏览器
栈的概念其实在咱们js中十分的重要,你们都知道咱们js是一个单线程语言,那么他单线程在哪里呢,就在他的主工做线程,也就是咱们常说的执行上下文,这个执行上下文就是栈空间,咱们来看一段代码:bash
console.log('1');
function a(){
console.log('2');
function b(){
console.log('3')
}
b()
}
a()
复制代码
咱们知道函数执行的时候会将这个函数放入到咱们的执行上下文中,当函数执行完毕以后会弹出执行栈,那么根据这个原理咱们就能知道这段代码的运行过程是socket
console.log('1')
,这个函数在调用的时候进入执行栈,当这句话执行完毕也就是到了下一行的时候咱们console
这个函数就会出栈,此时栈中仍然只有全局上下文a();
这句代码这个时候咱们的a函数就进入了执行栈,而后进入到咱们a
的函数内部中,此时咱们的函数执行栈应该是 全局上下文 —— a
console.log('2')
,执行栈变成 全局上下文——a——console
,接着咱们的console
运行完毕,咱们执行栈恢复成全局上下文 —— a
b()
;那么b
进入咱们的执行栈,全局上下文——a——b
,console.log('3')
的时候执行栈为全局上下文——a——b——console
,执行完毕以后回复成全局上下文——a——b
b
函数就执行完毕,而后就被弹出执行栈,那么执行栈就变成全局上下文——a
a
函数就执行完毕,而后就被弹出执行栈,那么执行栈就变成全局上下文
咱们的执行上下文的执行过程就是这样,是否是清楚了不少~函数
经过上面的执行上下文咱们能够发现几个特色:oop
队列是一种遵循先进先出(FIFO)的数据集合,新的条目会被加到队列的末尾,旧的条目会从队列的头部被移出。ui
这里咱们能够看到队列和栈不一样的地方是栈是后进先出相似于乒乓球盒,而队列是先进先出,也就是说最早进入的会最早出去。 一样咱们举个例子,队列就比如是咱们排队过安检,最早来到的人排在队伍的首位,后来的人接着排在队伍的后面,而后安检员会从队伍的首端进行安检,检完一我的就放行一我的,是否是这样的一个队伍就是先进先出的一个过程。
队列这里咱们就要提到两个概念,宏任务(macro task),微任务(micro task)。
Js的事件执行分为宏仁务和微任务
script
(全局任务),setTimeout
,setInterval
,setImmediate
,I/O ,UI renderingprocess.nextTick
, Promise.then
, Object.observer
, MutationObserver
.js执行代码的过程当中若是遇到了上述的任务代码以后,会先把这些代码的回调放入对应的任务队列中去,而后继续执行主线程的代码知道执行上下文中的函数所有执行完毕了以后,会先去微任务队列中执行相关的任务,微任务队列清空以后,在从宏仁务队列中拿出任务放到执行上下文中,而后继续循环。
浏览器环境
中的js事件环
//学了上面的事件环 咱们来看一道面试题
setTimeout(function () {
console.log(1);
}, 0);
Promise.resolve(function () {
console.log(2);
})
new Promise(function (resolve) {
console.log(3);
});
console.log(4);
//上述代码的输出结果是什么???
复制代码
思考思考思考思考~~~
正确答案是3 4 1
,是否是和你想的同样?咱们来看一下代码的运行流程
// 遇到setTimeout 将setTimeout回调放入宏仁务队列中
setTimeout(function () {
console.log(1);
}, 0);
// 遇到了promise,可是并无then方法回调 因此这句代码会在执行过程当中进入咱们当前的执行上下文 紧接着就出栈了
Promise.resolve(function () {
console.log(2);
})
// 遇到了一个 new Promise,不知道你们还记不记得咱们上一篇文章中讲到Promise有一个原则就是在初始化Promise的时候Promise内部的构造器函数会当即执行 所以 在这里会当即输出一个3,因此这个3是第一个输入的
new Promise(function (resolve) {
console.log(3);
});
// 而后输入第二个输出4 当代码执行完毕后回去微任务队列查找有没有任务,发现微任务队列是空的,那么就去宏仁务队列中查找,发现有一个咱们刚刚放进去的setTimeout回调函数,那么就取出这个任务进行执行,因此紧接着输出1
console.log(4);
复制代码
看到上述的讲解,你们是否是都明白了,是否是直呼简单~
那咱们接下来来看看node环境中的事件执行环
浏览器的 Event Loop 遵循的是 HTML5 标准,而 NodeJs 的 Event Loop 遵循的是 libuv标准,所以呢在事件的执行中就会有必定的差别,你们都知道nodejs实际上是js的一种runtime,也就是运行环境,那么在这种环境中nodejs的api大部分都是经过回调函数,事件发布订阅的方式来执行的,那么在这样的环境中咱们代码的执行顺序到底是怎么样的呢,也就是咱们不一样的回调函数到底是怎么分类的而后是按照什么顺序执行的,其实就是由咱们的libuv所决定的。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
复制代码
咱们先来看下这六个任务是用来干什么的
咱们再来看网上找到的一张nodejs执行图,咱们能看到图中有六个步骤 ,当代码执行中若是咱们遇到了这六个步骤中的回调函数,就放入对应的队列中,而后当咱们同步人物执行完毕的时候就会切换到下一个阶段,也就是timer阶段,而后timer阶段执行过程当中会把这个阶段的全部回调函数所有执行了而后再进入下一个阶段,须要注意的是咱们在每次阶段发生切换的时候都会先执行一次微任务队列中的全部任务,而后再进入到下一个任务阶段中去,因此咱们就能总结出nodejs的事件环顺序
那咱们来练练手~~~
// 咱们来对着咱们的执行阶段看看
let fs = require('fs');
// 遇到setTimeout 放入timer回调中
setTimeout(function(){
Promise.resolve().then(()=>{
console.log('then1');
})
},0);
// 放入微任务队列中
Promise.resolve().then(()=>{
console.log('then2');
});
// i/o操做 放入pending callbacks回调中
fs.readFile('./text.md',function(){
// 放入check阶段
setImmediate(()=>{
console.log('setImmediate')
});
// 放入微任务队列中
process.nextTick(function(){
console.log('nextTick')
})
});
复制代码
首先同步代码执行完毕,咱们先清空微任务,此时输出then2,而后切换到timer阶段,执行timer回调,输出then1,而后执行i/o操做回调,而后清空微任务队列,输出nextTick,接着进入check阶段,清空check阶段回调输出setImmediate
全部的规则看着都云里雾里,可是呢只要咱们总结出来了规律,理解了他们的运行机制那么咱们就掌握了这些规则,好咯,今天又学了这么多,不说了不说了,赶忙滚去写业务代码了.............