进入正文以前,首先要感谢各位大佬对本人第一篇掘金文章《ES6版Promise实现,给你不同的体验》的确定及指正,可能写的不尽人意,可是大家的点赞会是我继续分享的动力之一,只要努力过,结果就不要太在乎,由于努力以后的结果会让你满意!与诸位共勉!vue
好了,话很少说,接下来让咱们进入今天的话题。今天咱们来谈一谈事件环究竟是什么?javaScript
的事件环和Node
的事件环有什么区别?有没有一种无从下手的感受,别捉急,只要你仔细阅读本篇文章,相信可以解开心中的疑惑。 java
俗话说,工欲善其事必先利其器。在进入浏览器事件环和Node事件环情节以前呢,咱们有必要了解如下几组常见的概念。node
堆栈是在计算机领域不可忽视的概念,若是想要详细了解,请移步《堆栈_百度百科》。在javaScript中,栈中存的是基本数据类型,会自动分配内存空间,自动释放;堆中存的是引用数据类型,是动态分配的内存,大小不定也不会自动释放。git
heap
堆:也能够叫堆内存;是一种队列优先,先进先出的数据结构;stack
栈:又名'堆栈',也是一种数据结构,不过它是按照先进后出原则存储数据的。
嘻嘻😝,本身花了半天时间(夸张)画得,自我感受良好。github
既然咱们大体理解了堆和栈的含义,咱们来看一道面试题,如何用js代码实现队列和栈的功能呢?其实很简单啦,就是数组最基本经常使用的增删方法。面试
let arr = new Array();
arr.push(1);
arr.push(2);
arr.shift();
复制代码
let arr = new Array();
arr.push(1);
arr.push(2);
arr.pop();
复制代码
首先,咱们应该知道进程比线程要大。一个程序至少要有一个进程,一个进程至少要有一个线程。就拿咱们常常用的浏览器为例吧,为了更直观一些,先看下这张图片:ajax
JavaScript
最大的特色就是单线程的,其实应该说其主线程是单线程的。为何这么说呢?你想一下,若是js是多线程的,咱们在页面中这个线程要删了那个元素(不顺眼),另外一个线程呢我要保留那个元素(我罩着的),这样岂不是就乱套了。这也是为何JavaScript
执行同步代码,异步代码并不会阻塞代码的运行。setTimeout
、浏览器事件、ajax
的回调函数)setTimeout
定时器所在线程)ajax
请求线程)宏任务和微任务能够说都是异步任务。若是了解vue
源码的同窗,应该知道宏任务macrotask
和微任务microtask
这两个概念,他们的执行时机是不同的。vue
的$nextTick
的源码就是经过宏任务和微任务实现的。(能够去vue的github了解一下其实现原理)。数据库
macrotask
有:setTimeout
、setInterval
、 setImmediate
(ie浏览器才支持,node中本身也实现了)、MessageChannel
microtask
有:promise.then()
、process.nextTick
(node的)浏览器中,事件环的运行机制是,先会执行栈中的内容,栈中的内容执行后执行微任务,微任务清空后再执行宏任务,先取出一个宏任务,再去执行微任务,而后在取宏任务清微任务这样不停的循环,咱们能够看下面这张图理解一下:数组
从图中能够看出,同步任务会进入执行栈,而异步任务会进入任务队列(callback queue)等待执行。一旦执行栈中的内容执行完毕,就会读取任务队列中等待的任务放入执行栈开始执行。(图中缺乏微任务)promise
那么,咱们来道面试题检验一下,当咱们在浏览器中运行下面的代码,输出的结果是什么呢?
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(data => {
console.log('then3');
});
},1000);
Promise.resolve().then(data => {
console.log('then1');
});
Promise.resolve().then(data => {
console.log('then2');
setTimeout(() => {
console.log('setTimeout2');
},1000);
});
console.log(2);
// 输出结果:2 then1 then2 setTimeout1 then3 setTimeout2
复制代码
- 先执行栈中的内容,也就是同步代码,因此2被输出出来;
- 而后清空微任务,因此依次输出的是
then1
then2
;- 因代码是从上到下执行的,因此1s后
setTimeout1
被执行输出;- 接着再次清空微任务,
then3
被输出;- 最后执行输出
setTimeout2
Node
是基于V8引擎的JavaScript
运行环境,在处理高并发、I/O密集(文件操做、网络操做、数据库操做等)场景有明显的优点。Node的事件环机制与浏览器的是不太同样。 在Node运行环境中:
libuv
处理libuv
经过阻塞I/O和多线程实现异步I/O其实本质是在libuv
(一个高性能的,事件驱动的I/O库)内部有这样一个事件环机制。在Node启动时会初始化事件环,话很少说,先上图:
event loop
执行到某个阶段时会将当前阶段对应的队列依次执行。当队列执行完毕或者执行数量超过上限时,才会转入下一个阶段。node中的微任务在切换队列时执行。
timers
计时器:执行setTimeout
、setInterval
的回调函数;I/O callbacks
:执行I/O callback
被延迟到下一阶段执行;idle, prepare
:队列的移动,仅内部使用poll
轮询:检索新的I/O事件;执行I/O相关的回调check
:执行setImmediate
回调close callbacks
:执行close
事件的callback
,例如socket.on("close",func)
好了,接下来咱们先来看一道简单的测试题:
setTimeout(function () {
console.log('setTimeout');
});
setImmediate(function () {
console.log('setImmediate');
});
复制代码
这道题中若是你在
node
环境中多运行几回,就会发现输出顺序是不固定的。也就是说虽然上图中timers
队列在check
队列前面,可是setTimeout
和setImmediate
没有明确的前后顺序的,这是由node
的准备时间(准备工做会浪费必定的时间)致使的。
对应上面这道练习题,咱们再来看下面这道题:
let fs = require('fs');
fs.readFile('./1.txt', function () {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
});
复制代码
这道题的输出顺序是
setImmediate
而后setTimeout
,不管你运行多少次,结果顺序不会发生改变。这是由于fs
文件操做(I/O操做)属于属于poll
阶段,poll
阶段的下一阶段就是check
阶段,因此输出顺序是毋庸置疑的。
最后,让咱们来再看一道面试题加深对Node事件环的理解:
setImmediate(() => {
console.log('setImmediate1');
setTimeout(() => {
console.log('setTimeout1')
}, 0);
});
Promise.resolve().then(res=>{
console.log('then');
})
setTimeout(() => {
process.nextTick(() => {
console.log('nextTick');
});
console.log('setTimeout2');
setImmediate(() => {
console.log('setImmediate2');
});
}, 0);
复制代码
这道题的输出顺序是:
then、setTimeout二、nextTick、setImmediate一、setImmediate二、setTimeout1
,为何是这样的顺序呢?微任务nextTick
的输出是由于timers
队列切换到check
队列,setImmediate1
和setImmediate2
连续输出是因只有当前队列执行完毕后才能进去下一对列。
总结:今天的话题就先到这里了。可能本文有表述不清楚或者理解不对的地方,还请各位大佬给予指正,我会继续努力每周分享,万分感谢!若是您以为看了这篇文章有所收获,请不要忘了动动手指点个小❤️哦!让咱们一块儿荡起双桨,天天进步一点点,在技术的道路上越走越远!
原创不易,转载请注明出处!谢谢!