## 1.1 前言
### 1.1.1 什么是NodeJS
JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。
每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document之类的内置对象。而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fs、http等内置对象。
**简单的说, Node.js 就是运行在服务端的 JavaScript。**
### 1.1.2 作用
NodeJS的作者说,他创造NodeJS的目的是为了实现高性能Web服务器,他首先看重的是**事件机制和异步IO模型**的优越性,而不是JS。但是他需要选择一种编程语言实现他的想法,这种编程语言不能自带IO功能,并且需要能良好支持事件机制。JS没有自带IO功能,天生就用于处理浏览器中的DOM事件,并且拥有一大群程序员,因此就成为了天然的选择。
如他所愿,NodeJS在服务端活跃起来,出现了大批基于NodeJS的Web服务。而另一方面,NodeJS让前端众如获神器,终于可以让自己的能力覆盖范围跳出浏览器窗口,更大批的前端工具如雨后春笋。
因此,对于前端而言,虽然不是人人都要拿NodeJS写一个服务器程序,但简单可至使用命令交互模式调试JS代码片段,复杂可至编写工具提升工作效率。
## 1.2 模块
### 1.2.1 NodeJS模块
**js的天生缺陷——缺少模块化管理机制**
表现: JS中容易出现变量被覆盖,方法被替代的情况(即被污染)。特别是存在依赖关系时,容易出现错误。这是因为JS缺少模块管理机制,来隔离实现各种不同功能的JS判断,避免它们相互污染。
解决:经常采用命名空间的方式,把变量和函数限制在某个特定的作用域内,人肉约定一套命名规范来限制代码,保证代码安全运行。jQuery中有许多变量和方法,但是无法直接访问,必须通过jQuery,$调用各个方法。
**Commonjs规范**
不同于jQuery,Commonjs是一套规范,约定了js如何组织,如何编写,包括包,二进制,套接字,单元测试等等。大部分标准在拟定和讨论之中,首先把执行不同任务的代码块和代码文件看为独立的模块,每一个模块都是一个单独的作用域,但不是孤立的,可能存在依赖关系。每个模块分为三个部分,定义、标识和引用。这套规范与现实产品如node.js相互影响,良性循环。
**NodeJs的模块管理机制**
基于commonjs实现了模块管理系统。node中每一个js文件都是一个独立的模块,在其内部不需要有命名空间,不需要担心变量的污染和方法定义时的隔离。同时模块之间可以组合形成更强大的模块或功能包。npm即是用来管理各种功能包的。
**模块的类型**
①核心模块:如http、fs、path...
②本地模块:var util = require('./require.js');
③通过npm安装的第三方模块
在nodejs中可以通过文件路径或文件名来引入模块,如果用名称引用非核心模块的话,node最终会把模块映射到对应的模块文件的路径,那些包含了核心函数的核心模块会在node启动时被预先加载。
### 1.2.2 CommonJS规范
在CommonJS规范下,每个.js文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,a.js和b.js都申明了全局变量var s = 'xxx',但互不影响。
一个模块想要对外暴露变量(函数也是变量),可以用`module.exports = variable;`,一个模块要引用其他模块暴露的变量,用`var ref = require('module_name');`就拿到了引用模块的变量。
在编写每个模块时,都有`require、exports、module`三个预先定义好的变量可供使用。
**(1)require**
require函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./开头)或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的.js扩展名可以省略。
~~~javascript
//例子
var foo1 = require('./foo');
var foo2 = require('./foo.js');
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js');
// foo1至foo4中保存的是同一个模块的导出对象。
~~~
还可以使用以下方式加载和使用一个JSON文件。
~~~javascript
var data = require('./data.json');
~~~
**(2)exports**
exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。下例中导出了一个公有方法。
~~~javascript
exports.hello = function () {
console.log('Hello World!');
};
~~~
**(3)module**
通过module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象。例如模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式。
~~~javascript
module.exports = function () {
console.log('Hello World!');
};
~~~
详细的导入导出原理,可以查看这篇[教程](https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434502419592fd80bbb0613a42118ccab9435af408fd000)
## 1.3 模块初始化
一个模块中的 JS 代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。
**主模块**
通过命令行参数传递给 NodeJS 以启动程序的模块被称为**主模块**。主模块负责调度组成整个程序的其它模块完成工作。例如通过以下命令启动程序时,main.js 就是主模块。
~~~
node main.js
~~~
例如有以下目录。
~~~
- /NodeJS/hello/
- util/
counter.js
main.js
~~~
~~~javascript
//counter.js
var i = 0;
function count() {
return ++i;
}
exports.count = count;
~~~
该模块内部定义了一个私有变量 i,并在 exports 对象导出了一个公有方法 count。
主模块 main.js :
~~~javascript
var counter1 = require('./util/counter');
var counter2 = require('./util/counter');
console.log(counter1.count());
console.log(counter2.count());
console.log(counter2.count());
~~~
运行该程序的结果如下:
~~~javascript
$ node main.js
1
2
3
~~~
可以看到,counter.js 并没有因为被 require 了两次而初始化两次。