要理解Event Loop首先要学习几个概念,下面就经过这些概念来一步步解析Event Loopnode
进程是操做系统分配资源和调度任务的基本单位,线程是创建在进程上的一次程序运行单位,一个进程上能够有多个线程。ajax
JavaScript语言的一大特色就是单线程,也就是说,同一个时间只能作一件事。那么,为何JavaScript不能有多个线程呢?这样能提升效率啊。浏览器
JavaScript的单线程,与它的用途有关。做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。这决定了它只能是单线程,不然会带来很复杂的同步问题。好比,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准?bash
因此,为了不复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,未来也不会改变。网络
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。异步
这里所谓的单线程指的是主线程是单线程的,因此在Node中主线程依旧是单线程的。async
单线程特色是节约了内存,而且不须要在切换执行上下文。并且单线程不须要管锁的问题,这里简单说下锁的概念。例以下课了你们都要去上厕所,厕所就一个,至关于全部人都要访问同一个资源。那么先进去的就要上锁。而对于node来讲。下课了就一我的去厕所,因此免除了锁的问题!函数
堆和栈这两个字咱们已经接触多不少次,那么具体是什么存在栈中什么存在堆中呢?就拿JavaScript中的变量来讲:oop
一、基本类型学习
基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,咱们经过按值来访问的。
二、引用类型
引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的。以下图所示:栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间。因为这种值的大小不固定,所以不能把它们保存到栈内存中。但内存地址大小的固定的,所以能够将内存地址保存在栈内存中。 这样,当查询引用类型的变量时, 先从栈中读取内存地址, 而后再经过地址找到堆中的值。对于这种,咱们把它叫作按引用访问。
单线程就意味着,全部任务须要排队,前一个任务结束,才会执行后一个任务。若是前一个任务耗时很长,后一个任务就不得不一直等着。
若是排队是由于计算量大,CPU忙不过来,倒也算了,可是不少时候CPU是闲着的,由于IO设备(输入输出设备)很慢(好比Ajax操做从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程彻底能够无论IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回告终果,再回过头,把挂起的任务继续执行下去。
因而,全部任务能够分红两种,一种是同步任务(synchronous),另外一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。
具体来讲,异步执行的运行机制以下。(同步执行也是如此,由于它能够被视为没有异步任务的异步执行。)
(1)全部同步任务都在主线程上执行,造成一个执行栈(execution context stack)。
(2)主线程以外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
复制代码
主线程从"任务队列"中读取事件,这个过程是循环不断的,因此整个的这种运行机制又称为Event Loop(事件循环)。
为了更好地理解Event Loop,请看下图
整个的这种运行机制又称为Event Loop(事件循环)
Node.js也是单线程的Event Loop,可是它的运行机制不一样于浏览器环境。
请看下面的示意图
除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate。
process.nextTick方法能够在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")以前----触发回调函数。也就是说,它指定的任务老是发生在全部异步任务以前。setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务老是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
复制代码
上面代码中,因为process.nextTick方法指定的回调函数,老是在当前"执行栈"的尾部触发,因此不只函数A比setTimeout指定的回调函数timeout先执行,并且函数B也比timeout先执行。这说明,若是有多个process.nextTick语句(无论它们是否嵌套),将所有在当前"执行栈"执行。