在ES2015规范以前,JavaScript这门语言自己没有提供组织代码的方式, Node.js用CommonJS模块规范填补了这个空白,在这篇文章里面, 咱们将会讨论node.js的模块机制以及如何在项目中组织模块javascript
模块是代码结构的基本构建单元,模块容许你组织你本身的代码,隐藏私有数据,经过module.exports
暴露公共接口,每当你调用require
的时候,你就在加载一个模块vue
这是一个很简单的使用CommonJS的例子java
// multiply.js
function multiply(a, b) {
return a * b
}
module.exports = multiply
复制代码
这个multiply.js文件就是一个模块,要想使用它,咱们只须要require它便可node
const multiply = require('multiply')
console.log(multiply(2, 3)) // 6
复制代码
在背后,multiply.js被node.js用下面的方式包装了npm
(function (exports, require, module, __filename, __dirname) {
function multiply(a, b) {
return a * b
}
module.exports = add
})
复制代码
这就是为何你能够在你的代码中获取到像require和module这些全局变量,模块机制也保证了变量的做用域限制在本地而不会暴露到全局缓存
在Node中引入模块,须要经历以下3个步骤。
路径分析
文件定位
编译执行函数
在Node中,模块分为两类:一类是Node提供的模块,称为核心模块;另外一类是用户编写的模块,称为文件模块。post
核心模块在Node源代码的编译过程当中,编译进了二进制执行文件。在Node进程启动时,部分核心模块就被直接加载进内存中,因此这部分核心模块引入时,文件定位和编译执行这两个步骤能够省略掉,而且在路径分析中优先判断,因此它的加载速度是最 快的。
文件模块则是在运行时动态加载,须要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。ui
node的模块加载机制是在第一次调用requrie以后缓存模块,以后这个模块的调用都是从缓存中读取,也就是每当你使用`require('a-module'), 你将会获得a-module的同一个实例,这保证了模块是单例的,同一个模块无论require多少次,在应用中都保持一个状态this
你能够加载原生模块,路径引用来自你的文件系统或者已经安装的模块,若是传给require函数的标识符不是一个原生的模块也不是一个文件的引用('./../'),node.js将会查找已经安装的模块,他会遍历你的文件目录里面的node_modules文件夹,来查找引用的模块
处理模块加载的是node的核心模块是module.js
,能够在node仓库的lib/module.js找到它,其中最重要的是函数_load
和_complie
这个函数检查当前的模块是否是存在缓存中,若是在缓存中,则返回导出的对象
若是模块是原生的,会传入filename作参数,调用NativeModule.require()方法,返回结果
不然,这个函数为文件建立一个新模块而且将其保存在缓存中,返回导出对象,流程以下
编译函数在隔离的做用域或者沙箱里面运行文件,对这个文件暴露了require,module, exports这些帮助变量
在应用中,咱们在建立模块的时候须要处理好内聚和耦合的平衡,最理想的场景就是实现高内聚和低耦合
一个模块要想实现高内聚就须要专一一个功能,低耦合意味着模块不该该有全局或共享的状态,他们应该仅经过传递参数来通信,这样即便模块被替换也不须要改动太多的代码库
建议像下面这样单独暴露命名的函数和常量
const COUNT_NUMBER = 0
function count () { /* ... */ }
module.exports = {
COUNT_NUMBER,
count
}
复制代码
而不是暴露一个含有各类属性的对象
const COUNT_NUMBER = 0
function count () { /* ... */ }
let obj = {COUNT_NUMBER, count}
module.exports = obj
复制代码
有两种主要的方式写Node模块
一种是硬编码依赖,经过调用require,显式地加载一个模块到另一个模块,这种是最多见的使用方法,这种方式咱们用node来管理模块的生命周期,直接易懂,并且也方便调试
第二种是依赖注入的方式
这种方式在node的环境中不多使用,但它是一个颇有用的概念,依赖注入模式能够下降模块间的解耦,这种模式不是显式为模块定义依赖,而是从外面接收,所以很容易用一个有一样接口的模块来替代
咱们用工厂模式建立一个依赖注入的模块,来讲明下这种方式
class Car {
constructor (options) {
this.engine = options.engine
}
start () {
this.engine.start()
}
}
function create (options) {
return new Car(options)
}
module.exports = create
复制代码
当咱们使用create这个方法的时候,只要传递的options有engine属性,而且engine属性实现了start方法就能够了,换句话说,无论你是啥车,自行车也能够,只要有引擎,而且引擎有启动的方法,我就能够生产一台车,哪怕有一天你的车升级了,能够在水上开,空中飞也不要紧,这个create模块我依然不须要修改