模块化的好处: 为了更有效的组织代码,提高重用性,增大开发效率,我们会把项目拆分成不同模块,每个模块,职责单一,多人协同,高效运行,易于维护。
模块的发展历程
- js 不像其他高级语言有模块系统,标准库较少,缺乏包管理系统。
 - js 起初只有全局对象的形式,通过一个个小函数来实现不同的模块功能。
 - 渐渐发展,通过构建对象的形式,来封装不同的功能。
 - 继续发展,通过立即执行函数和闭包的形式来分离一个又一个的小组件。
 - 当对象多起来的时候,又开始通过命名空间,来实现分级管理。
 - 最终,历经十多年,社区渐渐发展壮大,
commonJS规范的提出成了javascript 历史上最重要的里程碑:Best Wishes : hope javascript can run everywhere! 
CommonJS规范
CommonJS只是一个规范,不是文件或者框架,这点一定要清楚。它的官方网址:http://www.commonjs.org/ ,CommonJS中大部分只是草案或者说是理论,在近几年的发展中已有显著成效,这些规范包括:
- 模块
 - 二进制
 - I/O流
 - 进程环境
 - 文件系统
 - 套接字
 - 单元测试
 - web服务器网关
 - 包管理等等。
 
在CommonJs规范中:
- 一个文件就是一个模块,拥有单独的作用域;
 - 普通方式定义的变量、函数、对象都属于该模块内;
 - 通过
require来加载模块; - 通过
exports和modul.exports来暴露模块中的内容; 
- 所有代码都运行在模块作用域,不会污染全局作用域;
 - 模块可以多次加载,但只会在第一次加载的时候运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果;
 - 模块的加载顺序,按照代码的出现顺序是同步加载的;
 
在后端
nodejs显然已经是CommonJS规范的最佳实践。
nodejs中的require的内部机制
我们知道,在nodejs 中每个文件就是一个独立的模块。在模块之间不会造成变量污染和命名冲突等问题。通过exports 或者 module.exports 输出功能或者说提供API,我们通过require 来引入一个模块,很像前端通过script标签来引入js文件。但是它的内部细节又如何呢,在朴灵的那本《深入浅出》中有关javascript的模块编译那块已经做了一些讲解,那么我们现在去繁留简来深入体验一把:
require 函数的源码可以这样理解,当然我们简化了不少东西,现在我们只把它抽离出来 :
1  | // 模拟require的实现  | 
说明: 上面举了一个小栗子来模拟nodejs中的require的实现,当然源码不会这样写,源码涉及的东西会更复杂些。上面只是一些帮助理解的小demo。
我们自己写的逻辑功能全在path所代表的文件模块中,在内部把所有功能挂在到module.exports 或者exports对象上,最终return 他们得以暴露。
我们可以看出 module.exports 和 exports 指向的是同一对象
在require一个模块时大致经历了这几步:
- 路径分析
 - 文件定位
 - 编译执行
 
一个小的 require 模块引入 案例
在 util.js文件模块中,我们这样写
1  | // 定义一个计算器对象  | 
在 index.js 文件中,我们这样写:
1  | var cal = require('./util');  | 
在 CLI 中,我们执行index模块,将会输出3$ node index
nodejs中的模块分类和加载顺序
nodejs 中核心模块和文件模块的引用方式:
- 核心模块: require(‘核心模块名’);
 - 文件模块: require(‘路径+文件名’); 路径可以用./代表的相对路径或者绝对路径,其中文件名可以省略后缀名。
 - 自定义模块:特殊的文件模块,可能是一个文件或者包的形式
 
模块加载顺序 :
- 优先从缓存加载
 - 核心模块:如http、fs、path等 (优先查找核心模块)
 - 文件模块: ./或../开始的相对路径文件模块,以/开始的绝对路径文件
 - 自定义模块:查找最费时
 
目录和包查找原则:
比如有如下的模块路径: 查找规则是沿路径向上逐级递归,直到根目录的node_modules目录:
├─/node_modules/
└─/home/node_modules/
└─/user/test/node_modules/
这就是自定义模块加载速度最慢的原因了。
当我们require 的标识符 不包含扩展名, node会按照
.js,.json,.node的次序补足扩展名 ,依次尝试。如果在require过程中,没有查找到对应文件,却得到一个目录,此时 node 会将当前目录当作一个包来处理。
此时,node 会查找目录下的
package.json文件,通过JSON.parse()解析包描述对象,从中拿到main属性指定的文件名进行定位。如果
main属性指定错误,或者没有package.json文件,那么node会将index作为默认的文件名,去依次查找index.js , index.json , index.node。在目录分析中没有定位到任何模块,那么它会遍历自己的上一级目录进行查找,如果还没找到,抛出查找失败的异常。
ps:
npm init -y:初始化一个package.json文件,加上-y就会默认生成该文件,无需一步一步填写