Node.js 的官方文档中有一段对 Node.js 的简介,如下。

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

大意就是说 Node.js 是基于 V8 的 JavaScript 运行时,事件驱动、非阻塞,因此轻量、高效。

寥寥数语,并没有说清楚 Node.js 到底是什么。参考了一些 Node.js 的官方文章以及社区里的分析,整理如下。

基础架构

要想深入理解 Node.js,我们需要把 Node.js 进行必要的拆解,了解每个组成部分的作用,它们之间如何交互,最终构成 Node.js 这个强大的运行时环境。

上图是 Node.js 的内部结构图。我们可以看到,自底向上主要可以分成三层:最底层是 Node.js 依赖的各种库,有 V8、libuv 等;中间层是各种 Binding,也就是胶水代码;最上层是应用代码,可使用 Node.js 的各种 API。

  • V8

    Google 开源的高性能 JavaScript 引擎,它将 JavaScript 代码转换成机器码,然后执行,因此速度非常快。V8 以 C++ 语言开发,Google 的 Chrome 浏览器正是使用的 V8 引擎。

  • libuv

    libuv 以 C 语言开发,内部管理着一个线程池。在此基础之上,提供事件循环(Event Loop)、异步网络 I/O、文件系统 I/O等能力。

  • 其他底层依赖库

    c-arescrypto (OpenSSL)http-parser 以及 zlib。这些依赖提供了对系统底层功能的访问,包括网络、压缩、加密等。

Node.js 底层的依赖库,有的以 C 语言开发,有的以 C++ 语言开发,如何让应用代码(JavaScript)能够与这些底层库相互调用呢?这就需要中间层的 Binding 来完成。Binding 是一些胶水代码,能够把不同语言绑定在一起使其能够互相沟通。在 Node.js 中,binding 所做的就是把 Node.js 那些用 C/C++ 写的库接口暴露给 JS 环境。

中间层中,除了 Binding,还有 Addon。Binding 仅桥接 Node.js 核心库的一些依赖,如果你想在应用程序中包含其他第三方或者你自己的 C/C++ 库的话,需要自己完成这部分胶水代码。你写的这部分胶水代码就称为 Addon。本质上都是完成桥接的作用,使得应用与底层库能够互通有无。

应用层的代码,就不必多言了,我们开发的应用、npm 安装的包都运行在这里。

事件循环 (event loop)

刚接触 Node.js 的时候,就知道 Node.js 有一个事件循环,类似于 while(true),但是不知道每次循环什么时候开始,什么时候结束,在每次循环中,Node.js 是如何处理同步与异步代码的。

要说事件循环,就不得不先说明一下 Node.js 的工作流程。下图可以简要说明。

一个 Node.js 应用启动时,V8 引擎会执行你写的应用代码,保持一份观察者(注册在事件上的回调函数)列表。当事件发生时,它的回调函数会被加进一个事件队列。只要这个队列还有等待执行的回调函数,事件循环就会持续把回调函数从队列中拿出并执行。

在回调函数执行过程中,所有的 I/O 请求都会转发给工作线程处理。libuv 维持着一个线程池,包含四个工作线程(默认值,可配置)。文件系统 I/O 请求和 DNS 相关请求都会放进这个线程池处理;其他的请求,如网络、平台特性相关的请求会分发给相应的系统处理单元进行处理。

安排给线程池的这些 I/O 操作由 Node.js 的底层库执行,完成之后触发相应事件,对应的事件回调函数会被放入事件队列,等待执行后续操作。这就是一个事件在 Node.js 中执行的整个生命周期。

前面说了,我们只知道 Node.js 有事件循环,但是不知道每次循环何时开始、何时结束。下面就简要说明一下每次循环的处理过程,详细内容请参考Node.js 官方说明

一次事件循环,大概可以分为如下几个阶段:

图中每一个方块,在事件循环中被称为一个阶段(phase)。

每个阶段都有自己独有的一个用于执行回调函数的 FIFO 队列。当事件循环进入一个指定阶段时,会执行队列中的回调函数,当队列中已经被清空或者执行的回调函数个数达到系统最大限制时,事件循环会进入下一个阶段。

上图中总共有6个阶段:

  • timers: 该阶段执行由 setTimeout()setInterval() 设置的回调函数。
  • I/O callbacks: 执行除了close 回调、timers 以及

    setImmediate() 设置的回调以外的几乎所有的回调。
  • idle,prepare: 仅供内部使用。
  • poll: 检索新的 I/O 事件;在适当的时候 Node.js 会阻塞等待。
  • check: 执行 setImmediate() 设置的回调。
  • close callbacks: 执行关闭回调。比如: socket.on('close', ...).

这里有个令人困惑的地方,I/O callbackspoll 这两个阶段有什么区别? 既然 I/O callbacks 中已经把回调都执行完了,还要 poll 做什么?

查阅了libuv 的文档后发现,在 libuv 的 event loop 中,I/O callbacks 阶段会执行 Pending callbacks 。绝大多数情况下,在 poll 阶段,所有的 I/O 回调都已经被执行。但是,在某些情况下,有一些回调会被延迟到下一次循环执行。也就是说,在 I/O callbacks 阶段执行的回调函数,是上一次事件循环中被延迟执行的回调函数。

还需要提到的一点是 process.nextTick()process.nextTick() 产生的回调函数保存在一个叫做 nextTickQueue 的队列中,不在上面任何一个阶段的队列里面。当当前操作完成后,nextTickQueue 中的回调函数会立即被执行,不管事件循环处在哪个阶段。也就是说,在 nextTickQueue 中的回调函数被执行完毕之前,事件循环不会往前推进。

测试与实践

如下代码中使用了 setTimeout(), setInterval(), setImmediate(), promise, process.nextTick(),可借助于输出结果,理解事件循环。

'use strict';

const fs = require('fs');

console.log('script start');

const interval = setInterval(() => {
console.log('setInterval')
}, 500); setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('promise 3');
}).then(() => {
console.log('promise 4');
process.nextTick(() => {
console.log('nextTick 1');
});
}).then(() => {
setTimeout(() => {
console.log('setTimeout 2');
Promise.resolve().then(() => {
console.log('promise 5');
}).then(() => {
console.log('promise 6');
process.nextTick(() => {
console.log('nextTick 2');
});
}).then(() => {
clearInterval(interval);
});
}, 0);
});
}, 1000); Promise.resolve().then(() => {
console.log('promise 1');
}).then(() => {
console.log('promise 2');
}); setImmediate(() => {
console.log('setImmediate 1');
}); console.log('script done');

执行结果为:

script start
script done
promise 1
promise 2
setImmediate 1
setInterval
setTimeout 1
promise 3
promise 4
nextTick 1
setInterval
setTimeout 2
promise 5
promise 6
nextTick 2

Node.js 原理简介的更多相关文章

  1. Node.js的简介和安装

    一.Node.js的简介和安装 a)       什么是Node.js? Node.js是一个开发平台 让JavaScript运行在服务器端的开发平台 ---简单点说就是用JavaScript写服务器 ...

  2. Node.js 入门简介

    Node.js简介 1.1 简介 V8引擎本身就是用于Chrome浏览器的JS解释部分,但是Ryan Dahl鬼才般的把这个V8搬到了服务器上,用于做服务器的软件. Node.js是一个专注于实现高性 ...

  3. JS, Node.js, npm简介

    序 听过JS,听过Node,也听过Node.js,还听过npm,然而并不是很清楚的知道都代表什么,这两天调接口,然后前端同学很忙,就自己把前端代码拿过来跑了,也趁机了解一下这几个概念,下边做个小的总结 ...

  4. Node.js Error简介以及捕获方式

    error的类型nodejs 的error 一般分为四种类型: 1.标准的 JavaScript 错误,例如 EvalError.SyntaxError.RangeError.ReferenceErr ...

  5. pace.js原理简介

    简介: 不少童鞋可能都使用过pace.js:http://github.hubspot.com/pace/docs/welcome/ 只要在页面上引入pace.js和相关的css,并不需要对业务逻辑做 ...

  6. Nodejs学习笔记(一)--- 简介及安装Node.js开发环境

    目录 学习资料 简介 安装Node.js npm简介 开发工具 Sublime Node.js开发环境配置 扩展:安装多版本管理器 学习资料 1.深入浅出Node.js http://www.info ...

  7. 【转】Nodejs学习笔记(一)--- 简介及安装Node.js开发环境

    目录 学习资料 简介 安装Node.js npm简介 开发工具 Sublime Node.js开发环境配置 扩展:安装多版本管理器 学习资料 1.深入浅出Node.js http://www.info ...

  8. 【Node.js】Node.js的安装

    Node.js的简介 简单的说,Node.js 是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一个事件 ...

  9. 【转】Node.js到底是用来做什么的

    Node.js的到底是用来做什么的 在阐述之前我想放一个链接,这是国外的一个大神,对于node.js非常好的一篇介绍的文章,英文比较好的朋友可以直接去阅读,本文也很大程度上参考了这篇文章,也同时感谢知 ...

随机推荐

  1. KEEP!

    [list][*]别问我前端有没有前途,我不知道,我只知道我现在喜欢前端,以后也应该喜欢.[*]别问我前端的工作好不好找,不管哪一职位,工作好不好找都是看你的水平.[*]别问我前端累不累,这世界就没有 ...

  2. SqlService 索引原理

    索引的概念 索引的用途:我们对数据查询及处理速度已成为衡量应用系统成败的标准,而采用索引来加快数据处理速度通常是最普遍采用的优化方法. 索引是什么:数据库中的索引类似于一本书的目录,在一本书中使用目录 ...

  3. js三大家族offset,scroll,cliennt的区别

    offset偏移(返回的是number类型) offsetLeft:返回盒子距离左边的距离 offsetTop:返回盒子距离顶部的距离 offsetHeight:返回盒子的高度,包括border,pa ...

  4. Redis 安装与简单使用

    安装 Redis 一般系统都会有软件管理工具,但是通常版本都不会太新,况且 Redis 的安装很简单,因此下面使用源码的安装方式. 下载源码 wget http://download.redis.io ...

  5. win10如何合并硬盘分区

    好多人都会讲电脑硬盘分成几个不同的区,以方便自己的资料的存储和查找,但不少人不知道如何合并已经分出的硬盘分区.以下是我的经验,与大家分享: 1.   首先,右击“此电脑”,在弹出来的右键菜单这种选择“ ...

  6. 使用promise手动封装ajax函数

    最近在做一个单页应用,node和浏览器仅通过json传输数据,因为是只有自己用等于是锻炼一下自己,所以也不用考虑seo的问题,node端我已经写好了,但是浏览器端想要用ajax原生太麻烦,用封装的函数 ...

  7. (1)写给Web初学者的教案-----学习Web的知识架构

    1:学习Web的知识架构 前文中我们简单的介绍了一些关于Web的基本知识,这里任老师再次强调一下凡是用浏览器打开的网站我们就称之为Web应用程序(B/S结构).除此之外其它需要下载安装的软件或是手机  ...

  8. 你真的会 snapshot 吗? - 每天5分钟玩转 OpenStack(163)

    ​这是 OpenStack 实施经验分享系列的第 13 篇. instance snapshot 操作可用于备份或者将 instance 保存为新的 image.如果在生产系统中执行 snapshot ...

  9. javaScript事件(六)事件类型之滚轮事件

    滚轮事件其实就是一个mousewheel事件,这个事件跟踪鼠标滚轮,类似Mac的触屏版. 一.客户区坐标位置 鼠标事件都是在浏览器视口的特定位置上发生的.这个位置信息保存在事件对象的clientX和c ...

  10. java实体属性对应mysql和SQL Server 和Oracle 数据类型对应

    1:Java数据类型与MySql数据类型对照表 类型名称 显示长度 数据库类型 JAVA类型 JDBC类型索引(int) VARCHAR L+N VARCHAR java.lang.String 12 ...