Node.js Event Loop与浏览器 Event Loop(事件环)

要理解Event Loop首先要学习几个概念,下面就经过这些概念来一步步解析Event Loopnode

一:进程与线程

进程是操做系统分配资源和调度任务的基本单位,线程是创建在进程上的一次程序运行单位,一个进程上能够有多个线程。ajax

1.1 为何JavaScript是单线程?

JavaScript语言的一大特色就是单线程,也就是说,同一个时间只能作一件事。那么,为何JavaScript不能有多个线程呢?这样能提升效率啊。浏览器

JavaScript的单线程,与它的用途有关。做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。这决定了它只能是单线程,不然会带来很复杂的同步问题。好比,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准?bash

因此,为了不复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,未来也不会改变。网络

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。异步

这里所谓的单线程指的是主线程是单线程的,因此在Node中主线程依旧是单线程的。async

1.2 其余线程

  • 浏览器事件触发线程(用来控制事件循环,存放setTimeout、浏览器事件、ajax的回调函数)
  • 定时触发器线程(setTimeout定时器所在线程)
  • 异步HTTP请求线程(ajax请求线程)

单线程特色是节约了内存,而且不须要在切换执行上下文。并且单线程不须要管锁的问题,这里简单说下锁的概念。例以下课了你们都要去上厕所,厕所就一个,至关于全部人都要访问同一个资源。那么先进去的就要上锁。而对于node来讲。下课了就一我的去厕所,因此免除了锁的问题!函数

二:队列和栈

2.1 栈内存or堆内存

堆和栈这两个字咱们已经接触多不少次,那么具体是什么存在栈中什么存在堆中呢?就拿JavaScript中的变量来讲:oop

  • 首先JavaScript中的变量分为基本类型和引用类型。
  • 基本类型就是保存在栈内存中的简单数据段,而引用类型指的是那些保存在堆内存中的对象。

一、基本类型学习

基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,咱们经过按值来访问的。

二、引用类型

引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的。以下图所示:栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间。因为这种值的大小不固定,所以不能把它们保存到栈内存中。但内存地址大小的固定的,所以能够将内存地址保存在栈内存中。 这样,当查询引用类型的变量时, 先从栈中读取内存地址, 而后再经过地址找到堆中的值。对于这种,咱们把它叫作按引用访问。

2.2 任务队列

单线程就意味着,全部任务须要排队,前一个任务结束,才会执行后一个任务。若是前一个任务耗时很长,后一个任务就不得不一直等着。

若是排队是由于计算量大,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,请看下图

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各类外部API,它们在"任务队列"中加入各类事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

  • 全部同步任务都在主线程上执行,造成一个执行栈
  • 主线程以外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的全部同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行
  • 主线程从任务队列中读取事件,这个过程是循环不断的

整个的这种运行机制又称为Event Loop(事件循环)

四:Node.js的Event Loop

Node.js也是单线程的Event Loop,可是它的运行机制不一样于浏览器环境。

请看下面的示意图

根据上图,Node.js的运行机制以下。

  • 咱们写的js代码会交给v8引擎进行处理
  • 代码中可能会调用nodeApi,node会交给libuv库处理
  • libuv库负责Node API的执行。它将不一样的任务分配给不一样的线程,造成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
  • 经过事件驱动的方式,将结果放到事件队列中,最终交给咱们的应用。

除了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语句(无论它们是否嵌套),将所有在当前"执行栈"执行。

相关文章
相关标签/搜索