介绍
工作原因接触了NodeJS,开始学习它,不过以前对这门语言不了解,学习了基本语法可以工作后就没有加深学习了。时间长了后发现这门语言比我想象中强大了很多,于是开始准备全面了解,深入学习,本文是学习了七天学会NodeJS之后的一个整理总结,入门向的一篇文章,旨在让我们可以全面了解这门语言,有兴趣的可以参考。
NodeJS基础
学习NodeJS,首先了解它是什么。
JS我们都知道–JavaScript,一门前端都在使用的脚本语言,而NodeJS就是运行在后端的js语言。换句话说,前端使用的JS是由浏览器进行解析运行的,而NodeJS就是一个独立的js解析器,随时随地,有node环境就可以解析运行。
NodeJS诞生的目的就是为了实现高性能的Web服务器(作者自己说的),至于为什么使用JS这门语言,主要有如下几点:
- JS这门语言自带的事件机制和异步IO模型(契合作者的需求)
- 同时JS没有自带的IO功能,不会有历史包袱(我理解是作者可以随意发挥)
- 有一大批前端程序员的拥护(用户众多)
- chrome浏览器的v8高性能JS引擎的出现(可移植,有大哥维护)
在这么多的优势的加护下,NodeJS也如作者期望的那样发展的欣欣向荣。
入门向的文章,安装运行少不了,不过原文已经很详细了,这里就不赘述了。

(为什么思维导图放在中间了,因为以下就开始将知识点了=-=)
模块是nodejs中很重要的一个概念,简单来说一个文件就是一个模块,文件名就是模块名。
模块中有三个预先定义好的变量:
- require: 加载其他模块
- exports: 当前模块的导出对象
- module: 当前模块的信息
关于模块的内容后面会专门写一篇文章详细介绍,此处略过。
最后注意一下,Node有个缓存机制,会将require的模块中编译执行后的对象进行缓存,二次加载时使用缓存的对象。
代码的组织和部署
搭建房子首先要规划好房子结构,而稍微大一点的项目免不了要准备好代码的目录结构和部署方式,这一章主要是组织结构的介绍。

模块解析规则
模块解析规则也就是模块导入的规则,主要分为三种
- 内置模块
- node_moudle模块
- NODE_PATH环境变量
原文说的很清楚了,这里补充的一点是查看当前的node_module目录可以参考如下方法:
包
包(PACKAGE)可以理解是多个模块的组合。复杂的模块往往由多个子模块组成,包就是多个子模块的集合。包中有两个重点:
- 入口模块
- package.json
入口模块的导出对象是包的导出对象,因此我们可以直接导入入口对象就算是导入了整个包。
不过这样做并不够灵活,如果入口模块名字改了,所有导入它的代码都要跟着修改,因为我们引入了package.json。
最简单的package.json文件我们可以这样写:
1 | { |
这样,加再模块时,只需要使用require('/home/user/lib/cat')
的方式就可以了,Node会自己根据package.json的配置找到入口模块。
package.json还有其他的成员,一个完整的package.json可以如下内容:
1 | { |
具体每个字段介绍可以参考JavaScript标准参考教程。
命令行程序
NodeJS可以编写命令行程序,只需要在程序前面加下如下内容
1 |
然后在/usr/local/bin/
下添加一个软链接就可以实现了,下面是一个很简单的实例:
Windows会有些不一样,我没用windows测试,这里也不介绍了,感兴趣自己看原文。
工程目录
代码的组织,可以参考如下布局:
1 | - /home/user/workspace/node-echo/ # 工程目录 |
没什么好聊的,只是个参考。
NPM
NPM是NodeJS的包管理工具,常见的使用场景如下:
- 允许用户从NPM服务器下载别人编写的三方包到本地使用。
- 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
- 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
NPM搭建了NodeJS的生态圈。
这里有一点注意的,我们可以在之前的package.json
文件中描述依赖的第三方包,直接通过npm install
进行安装,避免了环境搭建的烦恼(很棒的功能)。
npm -l
可以看到都有哪些命令,想看命令的详细介绍也可以使用npm help <command>
,文档非常全,这里就不赘述了。
文件操作
一个后端程序怎么能少得了文件操作。

文章有个copy文件的小程序很不错,可以引出一个知识点,程序如下:
1 | var fs = require('fs'); |
程序很简单,这里说他主要是下面这个小知识点:
process
是一个全局变量,可通过process.argv
获得命令行参数。由于argv[0]
固定等于NodeJS执行程序的绝对路径,argv[1]
固定等于主模块的绝对路径,因此第一个命令行参数从argv[2]
这个位置开始。
API
和文件相关的API有很多,这里拿出几个来对比
- Buffer
- Stream
- File System
Buffer
Buffer类的官方解释:
Buffer
类的实例类似于从0
到255
之间的整数数组(其他整数会通过& 255
操作强制转换到此范围),但对应于 V8 堆外部的固定大小的原始内存分配。Buffer
的大小在创建时确定,且无法更改。
在我看来,Buffer底层就是一个unsigned char
类型的数组,申请所需的堆空间并存储二进制数据。
使用场景;
多用于在 TCP 流、文件系统操作、以及其他上下文中与八位字节流进行交互。
相关api使用去官网查看文档。
Stream
照例来段官方解释:
流(stream)是 Node.js 中处理流式数据的抽象接口。
当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流。
流主要分为以下几种
- 可读流
- 可写流
- 读写流
- 转换流 Transform
在node中,这四种流都是EventEmitter的实例,它们都有close、error事件,可读流具有监听数据到来的data事件等,可写流则具有监听数据已传给低层系统的finish事件等,Duplex 和 Transform 都同时实现了 Readable 和 Writable 的事件和接口 。
当然,流也不是完美的,虽然消耗很少的内存,但那是比较理想的情况,如果读方没有尽快处理,会导致大量的数据被积压,而如何处理积压问题可以观看此文章数据流中的积压问题,官方文档,童叟无欺。
File System
正经的文件操作模块。
fs模块提供的API基本操作分为三类:
- 文件属性读写
- 文件内容读写
- 底层文件操作
如下为一个简单而又典型的异步IO模型读取文件的示例代码:
1 | fs.readFile(pathname, function (err, data) { |
网络操作
千呼万唤始出来,终于到了网络模块。
先来个官方文档的例子:
1 | var http = require('http'); |
这个是一个简单的HTTP服务器,打开浏览器访问该端口http://127.0.0.1:8124/
就能够看到效果。

API
与网络操作相关的API主要有如下几个:
- HTTP:网络服务核心模块
- HTTP/2:h2版本
- HTTPS:加密版本
- URL:url解析
- Zlib:提供了数据压缩和解压的功能
- net:用于创建Socket服务器或Socket客户端
下面介绍重点api:
HTTP
http模块是NodeJS中的核心模块,主要提供两种使用:
- 作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应。
- 作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。
服务端
本章刚开始的小例子就是一个服务端的实现,使用.createServer
方法创建一个服务器,然后调用.listen
方法监听端口,当有客户端请求过来时,就会有request
事件被触发,调用回调函数进行返回。
HTTP请求本质是一个数据流,由请求头和请求体组成,默认大家都了解,这里不再赘述。
服务端也是继承自EventEmitter的,而它所提供的事件如下:
request:当客户端请求到来时,该事件被触发,提供两个参数req和res,表示请求和响应信息。
connection:当TCP连接建立时,该事件被触发,提供一个参数socket,是net.Socket的实例。
close:当服务器关闭时,触发事件(注意不是在用户断开连接时)。
注意:request事件的参数req和res分别是http.IncomingMessage
和http.ServerResponse
的实例。
http.IncomingMessage是HTTP请求的信,其提供了三个事件
- data:当请求体数据到来时,该事件被触发,该事件提供一个参数chunk,表示接受的数据,如果该事件没有被监听,则请求体会被抛弃,该事件可能会被调用多次(这与nodejs是异步的有关系)
- end:当请求体数据传输完毕时,该事件会被触发,此后不会再有数据
- close:用户当前请求结束时,该事件被触发,不同于end,如果用户强制终止了传输,也是用close
而http.ServerResponse是返回给客户端的信息,决定了用户最终看到的内容,类似于上面,他有三个重要成员函数,用于返回响应头、内容以及结束请求:
res.writeHead():向请求的客户端发送响应头,该函数在一个请求中最多调用一次,如果不调用,则会自动生成一个响应头
res.write():向请求的客户端发送相应内容,data是一个buffer或者字符串,如果data是字符串,则需要制定编码方式,默认为utf-8,在res.end调用之前可以多次调用
res.end()):结束响应,告知客户端所有发送已经结束,当所有要返回的内容发送完毕时,该函数必需被调用一次,两个可选参数与res.write()相同。如果不调用这个函数,客户端将用于处于等待状态。
客户端
作为客户端就要简单很多了,先来个小栗子:
1 | var options = { |
这里用到了http.request
,另一个函数为http.get
,功能是作为客户端向http服务器发起请求。
因为GET
请求不需要请求体,所以请求不太一样,再来个小栗子:
1 | http.get('http://www.example.com/', function (response) {}); |
也没什么好介绍的了,详细介绍看官方文档吧。
HTTPS
https
模块与http
模块极为类似,区别在于https
模块需要额外处理SSL证书。
在服务端模式下,创建一个HTTPS服务器的示例如下:
1 | var options = { |
可以看到,与创建HTTP服务器相比,多了一个options
对象,通过key
和cert
字段指定了HTTPS服务器使用的私钥和公钥。
HTTPS与HTTP的区别这里就不介绍了,后面会专门出一篇文章,感兴趣的小伙伴也可以自己去查,网上有很多相关文章。
进程管理
NodeJS可以感知和控制自身进程的运行环境和状态,也可以创建子进程并与其协同工作,这使得NodeJS可以把多个程序组合在一起共同完成某项工作,并在其中充当胶水和调度器的作用。

API
进程管理有关的API如下:
- process: 当前进程管理
- child_process: 创建和控制子进程
- cluster: 对
child_process
模块的封装,充分利用多核CPU
process
process是一个全局对象,提供了当前NodeJS进程的信息并可以对其进行控制,以下为process所提供的一系列属性:
process.argv
:返回一个数组,成员是当前进程的所有命令行参数。process.env
:返回一个对象,成员为当前Shell的环境变量,比如process.env.HOME
。process.installPrefix
:返回一个字符串,表示 Node 安装路径的前缀,比如/usr/local
。相应地,Node 的执行文件目录为/usr/local/bin/node
。process.pid
:返回一个数字,表示当前进程的进程号。process.platform
:返回一个字符串,表示当前的操作系统,比如Linux
。process.title
:返回一个字符串,默认值为node
,可以自定义该值。process.version
:返回一个字符串,表示当前使用的 Node 版本,比如v7.10.0
。
当然,还有各种事件以及成员方法,方法简要说下吧,事件自己去看。
process.chdir()
:切换工作目录到指定目录。process.cwd()
:返回运行当前脚本的工作目录的路径。process.exit()
:退出当前进程。process.getgid()
:返回当前进程的组ID(数值)。process.getuid()
:返回当前进程的用户ID(数值)。process.nextTick()
:指定回调函数在当前执行栈的尾部、下一次Event Loop之前执行。process.on()
:监听事件。process.setgid()
:指定当前进程的组,可以使用数字ID,也可以使用字符串ID。process.setuid()
:指定当前进程的用户,可以使用数字ID,也可以使用字符串ID。
child_process
child_process模块用来创建和控制子进程,其核心方法为child_process.spawn()
,常用的四个方法如下:
- exec:用于执行bash命令,它的参数是一个命令字符串。
- execFile:直接执行特定的程序,参数作为数组传入,不会被bash解释
- fork:创建一个子进程,执行Node脚本
- spawn:创建一个子进程来执行特定命令,没有回调函数
每个都有自己的用途,一个简单的实例如下:
1 | var exec = require('child_process').exec; |
具体介绍和使用官方写的很详细了,不过有一段话这里可以介绍下
child_process.execFile()
: 类似于child_process.exec()
,但是默认情况下它会直接衍生命令而不先衍生 shell。
这是一段对execFile的介绍,看到这里后对这段话很不理解,于是去查看了源码,如下:
1 | function normalizeExecArgs(command, options, callback) { |
上面这段是exec()方法的实现,可以看到是调用了execFile();

官网中对shell参数有如上图描述,在上述代码中我们可以看到exec()传进来的默认为true,所以会启动shell,也就是可以执行一些shell命令和脚本,而execFile()默认为false,则不会启动shell,可以执行一些可运行的程序,效率会高过exec()。
cluster
cluster意为集群,表示多个Node进程构成的服务。cluster封装了child_process.fork方法创建node子进程.。利用cluster模块,我们可以创建多进程的Web服务器,充分利用多核处理器的计算资源。下面给出一个具体示例:
1 | const cluster = require('cluster'); |
我们将master称为主进程,worker进程称为工作进程,利用cluster模块,使用Node的cluster模块封装好的API、IPC通道和调度机制可以非常简单的创建包括一个master进程下HTTP代理服务器 + 多个worker进程多个HTTP应用服务器的架构。
我们可以看到多个子进程共同监听了同一端口,这是违背规则的,那么Node是如何实现的呢?
这是因为,子进程并没有创建ServerSocket作监听,而是交由父进程创建ServerSocket监听指定端口,接收到连接后创建Socket,而得到的请求会根据“指定的分发规则”通过IPC发送给子进程,子进程处理后的结果再通过IPC由父进程转发给请求方。
最后,cluster的请求分发策略有两种:
- Round-Robin法:即轮询,依次循环将请求分配给子线程。
- 共享服务端socket方式:由操作系统进行调度。
cluster的底层对master和child有着不同的实现:

上图为cluster实现的源码,具体原理还需要对其进行源码解读。
异步编程
先来说异步是什么,我的理解是:
异步就是有事件发生找个人去处理,自己继续忙自己的任务,等那个人处理完了通知到你,你再验收。
我们都知道,Node是单线程的,可是如果是单线程,又如何完成异步机制和事件机制呢?
实际上,NodeJS的单线程是指的只有一个主线程,其他线程包括而不限于如下几种:
- js引擎执行线程
- 定时器线程
- 异步IO线程
这些线程可以称之为工作线程,这种机制避免了node因为线程切换造成的损耗,使得Node非常适合IO密集型应用。而本章的异步编程主要是创建工作线程与主线程并发执行,但需要等主线程空闲时才能执行回调函数。

回调
在代码中,异步编程的直接体现就是回调。异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了,比如如下程序:
1 | function heavyCompute(n, callback) { |
显然,并没有异步执行,那如果想写一个异步的程序需要怎么写呢,学到的一个很简单的实例:
1 | setTimeout(function () { |
通过调用setTimeout()这个原生的异步函数来实现异步。
你说这耍赖,不是你自己实现的,那再来个异步遍历数组的例子:
1 | (function (i, len, count, callback) { |
看到这里就结束了,有人可能好奇,不是七天么,这不是才六章吗?
第七天是个综合示例,自己照着敲学习吧🤪。