欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

node的模块机制

程序员文章站 2022-05-30 14:04:54
...

CommonJS承担了拯救者的角色

在ES2015规范之前,JavaScript这门语言本身没有提供组织代码的方式, Node.js用CommonJS模块规范填补了这个空白,在这篇文章里面, 我们将会讨论node.js的模块机制以及如何在项目中组织模块

模块是什么

模块是代码结构的基本构建单元,模块允许你组织你自己的代码,隐藏私有数据,通过module.exports暴露公共接口,每当你调用require的时候,你就在加载一个模块

这是一个很简单的使用CommonJS的例子

// multiply.js
function multiply(a, b) {
    return a * b
}
module.exports = multiply
复制代码

这个multiply.js文件就是一个模块,要想使用它,我们只需要require它即可

    const multiply = require('multiply')
    console.log(multiply(2, 3)) // 6
复制代码

在背后,multiply.js被node.js用下面的方式包装了

(function (exports, require, module, __filename, __dirname) {
 function multiply(a, b) {
    return a * b
}

  module.exports = add
})
复制代码

这就是为什么你可以在你的代码中获取到像require和module这些全局变量,模块机制也保证了变量的作用域限制在本地而不会暴露到全局

Node的模块实现

在Node中引入模块,需要经历如下3个步骤。
路径分析
文件定位
编译执行

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

核心模块在Node源代码的编译过程中,编译进了二进制执行文件。在Node进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以它的加载速度是最 快的。
文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。

node的模块加载机制是在第一次调用requrie之后缓存模块,之后这个模块的调用都是从缓存中读取,也就是每当你使用`require('a-module'), 你将会得到a-module的同一个实例,这保证了模块是单例的,同一个模块不管require多少次,在应用中都保持一个状态

你可以加载原生模块,路径引用来自你的文件系统或者已经安装的模块,如果传给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项目实现缓存的最佳方案

转载于:https://juejin.im/post/5cde5ad76fb9a07ee565ecd9