node的模块机制

CommonJS承担了拯救者的角色

在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的模块实现

在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

Module._load

这个函数检查当前的模块是否是存在缓存中,若是在缓存中,则返回导出的对象

若是模块是原生的,会传入filename作参数,调用NativeModule.require()方法,返回结果

不然,这个函数为文件建立一个新模块而且将其保存在缓存中,返回导出对象,流程以下

Module._compile

编译函数在隔离的做用域或者沙箱里面运行文件,对这个文件暴露了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模块我依然不须要修改

往期推荐

nodejs进阶系列

1 npm的使用和最佳实践
2 手把手教你用npm发布包

vue系列

和尤雨溪一块儿进阶vue
vue项目实现缓存的最佳方案

相关文章
相关标签/搜索