一、什么是 CommonJs?

 JavaScript 是一个强大面向对象语言,它有很多快速高效的解释器。然而, JavaScript 标准定义的 API 是为了构建基于浏览器的应用程序。并没有制定一个用于更广泛的应用程序的标准库。CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准的缺陷。它的终极目标就是:提供一个类似 Python,Ruby  和 Java  语言的标准库,而不只是停留在小脚本程序的阶段。用 CommonJS API 编写出的应用,不仅可以利用 JavaScript 开发客户端应用,而且还可以编写以下应用:服务器端 JavaScript 应用程序。(nodejs)、命令行工具、桌面图形界面应用程序。

CommonJS 就是模块化的标准,nodejs 就是 CommonJS(模块化)的实现。

二、Nodejs 中的模块化:Node 应用由模块组成,采用 CommonJS 模块规范。

2.1 在 Node 中,模块分为两类:

 一类是 Node 提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。

• 核心模块部分在 Node 源代码的编译过程中,编译进了二进制执行文件。在 Node 进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以它的加载速度是最快的。如:HTTP 模块 、URL 模块、Fs 模块都是 nodejs 内置的核心模块,可以直接引入使用。

• 文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程、速度相比核心模块稍微慢一些,但是用的非常多。这些模块需要我们自己定义。接下来我们看一下 nodejs 中的自定义模块。

2.2 CommonJS(Nodejs)中自定义模块的规定:

1.我们可以把公共的功能抽离成为一个单独的 js 文件作为一个模块,默认情况下面这个模块里面的方法或者属性,外面是没法访问的。如果要让外部可以访问模块里面的方法或者属性,就必须在模块里面通过 exports 或者 module.exports 暴露属性或者方法。

2.在需要使用这些模块的文件中,通过 require 的方式引入这个模块。这个时候就可以使用模块里面暴露的属性和方法。


模块(module)概述 

● 在Node.js中,以模块为单位划分所有功能,并且提供了一个完整的模块加载机制,这时的我们可以将应用程序划分为各个不同的部分。不会将所有功能写在一个JS文件中,肯定要有MVC相关模式。
● 狭义的说,每一个JavaScript文件都是一个模块;而多个JavaScript文件之间可以相互require,他们共同实现了一个功能,他们整体对外,又称为一个广义上的模块。
● 它的好处显而易见:
1. 减少重复代码量,增加可读性
2. 方便进行代码规划
3. 方便使用第三方模块

模块的概念 
● Node.js中,一个JavaScript文件中定义的变量、函数,都只在这个文件内部有效。当需要从模块外部引用这些变量、函数时,必须使用 exports 对象进行暴露。使用者要用 require() 命令引用这个JS文件。
let firstModule = '我是mymodule文件中的firstModule';
exports.firstModule = firstModule;
使用方式如下:
let firstModule = require('./module/mymodule.js')
console.log(firstModule);
● 一个JavaScript文件,可以向外 exports 无数个变量、函数。但是require的时候,仅仅需要require这个JS文件一次。使用它的变量、函数的时候,用点语法即可。所以,无形之中,增加了一个顶层命名空间。
Node中,js文件和js文件,就是被一个个exports和require构建成为网状的。不是靠html文件统一在一起的。
● 还可以将一个JavaScript文件中,描述一个类。用    module.exports = 构造函数名;   的方式向外暴露一个类。
也就是说,js文件和js文件之间有两种合作的模式:
1) 某一个js文件中,提供了函数,供别人使用。 只需要暴露函数就行了; exports.msg=msg;
2) 某一个js文件,描述了一个类。   module.export = People;

可以理解为:exports = module.exports = {};  
1、exports是module.exports的一个引用
2、require引用模块后,返回给调用者的是module.exports而不是exports
3、exports.xxx,相当于在导出对象上挂属性,该属性对调用模块直接可见
4、exports =相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性
5、如果此模块是一个类,就应该直接赋值module.exports,这样调用者就是一个类构造器,可以直接new实例

总结下,有两点:
1、对于要导出的属性,可以简单直接挂到exports对象上
2、对于类,为了直接使导出的内容作为类的构造器可以让调用者使用new操作符创建实例对象,应该把构造函数挂到module.exports对象上,不要和导出属性值混在一起 

我们只需知道三点就知道 exports 和 module.exports 的区别了: 
1、module.exports 初始值为一个空对象 {} 
2、exports 是指向的 module.exports 的引用 
3、require() 返回的是 module.exports 而不是 exports 
let utils = {
add:function (x,y) {
return x+y;
},
sub:function (x,y) {
return x - y;
}
};

module.exports = utils;

如果在require命令中,这么写:   

const a = require('a.js');//没有写./, 所以不是一个相对路径。是一个特殊的路径

那么Node.js将该文件视为node_modules目录下的一个文件


node_modules文件夹 
如果在require命令中,这么写
let person = require('person.js');

那么Node.js将该文件视为node_modules目录下的一个文件。

node_modules文件夹并不一定在同级目录里面,在任何直接祖先级目录中,都可以。甚至可以放到NODE_PATH环境变量的文件夹中。这样做的好处稍后你将知道:分享项目的时候,不需要带着modules一起给别人。

使用代码:console.log(module.paths); 我们会发现输出如下内容;

[
'D:\\WS\\ui\\testnodejs01\\modules\\node_modules',
'D:\\WS\\ui\\testnodejs01\\node_modules',
'D:\\WS\\ui\\node_modules',
'D:\\WS\\node_modules',
'D:\\node_modules'
]

● 我们可以使用文件夹来管理模块,比如:  let common = require('common');   

那么Node.js将会去寻找node_modules目录下的common文件夹中的index.js去执行。也就是说,我们可以使用文件夹来管理模块。

查找顺序:从当前node_modules文件夹开始查找模块,最后查找NODE_PATH环境变量文件夹


package.json文件 

● 如果使用文件夹来统筹管理一个模块,那么使用package.json文件来进行配置和管理,是非常必要的。
每一个模块文件夹中,推荐都写一个package.json文件,这个文件的名字不能改。node将自动读取里面的配置。有一个main项,就是入口文件(默认为index.js,如果存在package.json文件,则按文件中为准):
{
"name": "myfirstnodejs",
"version": "1.0.1",
"main": "app.js"
}
● package.json文件,放到模块的目录中。
大家如果使用WebStorm创建一个Node.js项目,会自动生成一个package.json文件,如下:
{
"name": "testnodejs01",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

npm 

● Node.js是一个怎样的世界?
    答:是一个引用别人的module做成自己的项目,而别人的module又是引用别别人的module的,别别别人的module又是引用别别别别人的module的……
● npm的主要职责是安装开发包和管理依赖项。
● 安装开发包,使用npm install命令;更新,使用npm update命令。
● 管理依赖项,借助package.json文件。最简单生成package.json的方法就是npm init

我们知道了模块就是一些功能的  ,所以一些成熟的、经常使用的功能,都有人封装成为了模块。并且放到了社区中,供人免费下载。
这个伟大的社区,叫做npm。 也是一个工具名字  node package management,官方网站:https://www.npmjs.com/ 

去社区搜索需求,然后点进去,看api。
如果要配置一个模块,那么直接在cmd使用:npm install 模块名字
就可以安装。 模块名字全球唯一。
安装的时候,要注意,命令提示符的所在位置。

1.我们的依赖包,可能在随时更新,我们永远想保持更新,或者某持某一个版本;
2.项目越来越大的时候,给别人看的时候,没有必要再次共享我们引用的第三方模块。

我们可以用package.json来管理依赖。
在cmd中,使用npm init可以初始化一个package.json文件,用回答问题的方式生成一个新的package.json文件。
使用 npm install 将能安装所有依赖。
npm也有文档,这是package.json的介绍:


require()别的js文件的时候,将执行那个js文件。
注意:require()中的路径,是从当前这个js文件出发,找到别人。而fs是从命令提示符找到别人。
所以,项目根目录是D:/testnodejs,其下有一个a.js,test文件夹中有b.js、c.js、1.txt
1、a中引用b:
let b = require("./test/b.js");
2、b中引用c:
let b = require("./c.js");
但是,fs等其他的模块用到路径的时候,都是相对于cmd命令光标所在位置。
3、所以,在b.js中想读1.txt文件,推荐用绝对路径
const fs = require("fs");

fs.readFile(__dirname + "/1.txt", function (err, data) {
if (err) {
throw err;
}
console.log(data.toString());
});