Node.js之模块化

436 阅读6分钟

模块化的基本概念

1、 什么是模块化

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。

  • 编程领域中的模块化

    编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并相互以赖的多个小模块

    把代码进行模块化拆分的好处:

    1. 提高了代码的复用性
    2. 提高了代码的可维护性
    3. 可以实现按需加载
  • 模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。

    例如:

    • 使用什么样的语法格式来引用模块
    • 在模块中使用什么样的语法格式向外暴露成员

    模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。

2、 Node.js 中模块的分类

  • Node.js 中模块的分类

    Node.js中根据模块来源的不同,将模块分为了3大类,分别是:

    • 内置模块(内置模块是由Node.js官方提供的,例如fs、path、http等)
    • 自定义模块(用户创建的每个.js文件,都是自定义模块)
    • 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
  • 加载模块

    使用强大的require()方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:

    // 1. 加载内置的 fs 模块
    const fs = require('fs')
    ​
    // 2. 加载用户的自定义模块
    const custom = require('./custom.js')
    ​
    // 3. 加载第三方模块
    const moment = require('moment')
    

    注意:使用require()方法加载其他模块时,会执行被加载模块中的代码。

3、 Node.js 中的模块作用域

  1. 什么是模块作用域

    函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

  2. 模块作用域的好处

    防止了全局变量污染的问题

4、 向外共享模块作用域中的成员

  1. module对象

    在每个 .js 自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息

  2. module.exports对象

    在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。

    外界用 require()方法 导入自定义模块时,得到的就是 module.exports 所指向的对象。

  3. 共享成员时的注意点

    使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准

  4. exports 对象

    由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象。默认情况下,exports 和 module.exports 指向同一个对象。 最终共享的结果,还是以 module.exports 指向的对象为准。

  5. exports 和 module.exports 的使用误区

    时刻谨记,require() 导入模块时,得到的永远是 module.exports 指向的对象。

exports与mudule.exports使用注意点.png 注意: 当module.exports指向新对象之后,以module.exports指向的对象为准,而不是exports指向的对象。

注意: 为了防止混乱,建议大家不要在同一个模块中同时使用 exportsmodule.exports

5、 Node.js中的模块化规范

Node.js 遵循了 CommonJS模块化规范,CommonJS 规定了模块的特性各个模块之间如何相互依赖

CommonJS规定:

  • 每个模块内部,module变量代表当前模块。
  • module 变量是一个对象,它的exports属性(即module.exports是对外的接口
  • 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块

模块的加载机制

1、优先从缓存中加载

模块在第一次加载后会被缓存。这也意味着多次调用 require()不会导致模块的代码被多次执行。(如下图导入三次模块,只打印了一次'ok',说明代码只执行了一次)

模块的加载机制.png

注意: 不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率

2、内置模块的加载机制

内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。

例如,require('fs') 始终返回的内置的 fs 模块,即使在 node_modules目录下有名字相同的包也叫做 fs。

3、自定义模块的加载机制

使用 require() 加载自定义模块时,必须指定以 ./ 或 ../ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 ../ 这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。

同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载
  2. 补全 .js 扩展名进行加载
  3. 补全 .json 扩展名进行加载
  4. 补全 .node 扩展名进行加载
  5. 加载失败,终端报错

4、第三方模块的加载机制

如果传递给 require()的模块标识符不是一个内置模块,也没有以 './' 或 '../' 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块

如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。

例如,假设在 'C:\Users\leilei\project\foo.js' 文件里调用了 require('tools'),则Node.js 会按以下顺序查找:

  1. C:\User\leilei\project\node_modules\tools
  2. C:\User\leilei\node_modules\tools
  3. C:\User\node_modules\tools
  4. C:\node_modules\tools

5、目录作为模块

当把目录(没有指定确切文件,只是一个文件夹)作为模块标识符,传递给 require()进行加载的时候,有三种加载方式:

  1. 在被加载的目录下查找一个叫做 package.json的文件,并寻找 main 属性,作为 require() 加载的入口
  2. 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的index.js文件
  3. 如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx'