CommonJS

104 阅读2分钟

Module

  • Node 内部提供了一个 Module 构建函数,所有模块都是 Module 的实例。
  • 每个模块内部都有一个 module 实例,代表其所在模块,它包含以下属性: image.png
    • id:标识符,通常是带有绝对路径的模块文件名,若为入口文件,则为 .
    • path:所在目录
    • exports:对外输出的内容
    • filename:所在目录及文件名
    • loaded:是否已经加载完成
    • children:调用的其他模块
    • pathsrequire 包时的查找路径
  • Node 为了方便给每个模块都提供了一个 exports 变量,它的值为 module.exports 对象的地址,所以不可以对 exports 重新赋值,这会让它断开与 module.exports 的联系,故不推荐使用 exports

require

  • require 可以读取并执行一个模块,然后返回其 module.exports 的浅拷贝,若导出的基本类型在导出后在模块又内部更改了,不会影响导出的值。

  • 单个模块文件仅会在首次加载时执行一次,之后其 module.exports 则会被挂载到 Module._cache 上,属性名为模块文件所在绝对路径,后续再次加载该模块,会直接取出缓存的值,不再执行。

    image.png

  • 如果发生模块的循环加载,即 A 加载 B,B 又加载 A,则 B 将加载 A 的不完整版本。

  • require 参数识别规则:

    • 若指定文件拓展名,则直接根据路径查找具体文件。
    • 若不指定文件拓展名,如 require("./haha"),则按以下流程:
      • 查找 haha 无后缀名文件,若找到则将其当作 JS 文件处理。
      • 若未找到则依次为其添加 .js .json .node 后缀查找。
      • 若依旧没有,则将 haha 当作目录名,找到该目录下的 package.json 文件中的 main 字段作为入口文件。
      • main 错误或 package.json 不存在则尝试读取对应目录下的 index.js/json/node 文件。
    • 若参数为非路径形式(如 require("haha")),则按以下流程:
      • 先判断是否为 Node 内置模块(如 fs、http)
      • 若不是则按照 module.paths 依次查找,找到对应目录下的 package.json 文件里的 main 作为入口文件。
      • main 错误或 package.json 不存在则尝试读取对应目录下的 index.js/json/node 文件。

require 过程

image.png

  • 可以看到 require 内部调用了 Module._load 方法用于加载模块
  • Module._load
    • 首先到 Module._cache 中查找缓存
    • 若没有则创建一个新的 Module 实例
    • 使用 module.load(Module.prototype.load) 加载指定文件,module.load 内会调用 module._compile(Module.prototype._compile) 执行代码。
    • module._compile 为同步代码,故 module.load 需等待模块代码执行完毕才会返回 module.exports
  • Module.prototype._compile
    • 通过 makeRequireFunction 生成一个 require 函数,生成的这个require 函数内部其实还是调用 module.require(Module.prototype.require),只是裹了一层,之后还将 resolve main cache 挂载到了导出的 require 上。
    • 模块代码被包裹到一个函数中执行,避免全局环境污染,并给定参数 require exports module __dirname __filename

References