Node commonjs 规范

106 阅读1分钟

禁止转载,侵权必究!

一.commonjs 规范

  • 1.每个 js 文件都是一个模块
  • 2.每个模块想去引⽤别⼈的模块,需要采⽤ require 语法 => import
  • 3.每个模块想被别⼈使⽤需要采⽤ module.exports 进⾏导出 => export
const path = require("path");
const fs = require("fs");
const vm = require("vm");
function Module(id) {
  this.id = id;
  this.exports = {}; // 存储加载后的结果
}
Module._extensions = {
  ".js"(module) {
    // 读取⽂件内容
    const content = fs.readFileSync(module.id, "utf8");
    // 给读取到的内容包裹函数,实现隔离
    let wrapperFn = vm.compileFunction(content, [
      "exports",
      "require",
      "module",
      "__filename",
      "__dirname",
    ]);
    let exports = this.exports;
    let thisValue = exports;
    let require = req;
    let filename = module.id;
    let dirname = path.dirname(filename);
    // 调⽤函数传⼊所需的参数
    Reflect.apply(wrapperFn, thisValue, [
      exports,
      require,
      module,
      filename,
      dirname,
    ]);
  },
};
Module._resolveFilename = function (id) {
  // 处理成需要的⽂件名
  const exts = Object.keys(Module._extensions);
  for (let i = 0; i < exts.length; i++) {
    const fileUrl = path.resolve(__dirname, id) + exts[i];
    if (fs.existsSync(fileUrl)) return;
    fileUrl;
  }
  throw new Error("模块找不到");
};
Module.prototype.load = function (filename) {
  let ext = path.extname(filename);
  Module._extensions[ext](this);
};
Module._cache = {};
function req(id) {
  // 1.根据传⼊的路径解析出绝对路径
  let absPath = Module._resolveFilename(id);
  // 2.创建模块
  const module = new Module(absPath);
  // 3.根据路径加载模块
  module.load(absPath);
  // 4.将加载后的结果返回
  return module.exports;
}
const str = req("./a"); // .js 后缀 .json 后缀, 默认先找js文件
console.log(str);

1.模块的缓存

Module._cache = {};
function req(id) {
  // 1.根据传⼊的路径解析出绝对路径
  let absPath = Module._resolveFilename(id);
  const existsModule = Module._cache[absPath];
  if (existsModule) {
    return existsModule.exports; // 如果缓存过,则直接返回缓存过的结果
  }
  // 2.创建模块
  const module = new Module(absPath);
  Module._cache[absPath] = module;
  // 3.根据路径加载模块
  module.load(absPath);
  // 4.将加载后的结果返回
  return module.exports;
}

2.json 模块加载策略

Module._extensions = {
  // ...
  ".json"(module) {
    // 读取⽂件内容
    const content = fs.readFileSync(module.id, "utf8");
    return JSON.parse(content);
  },
};

3.循环引⽤问题

// module-a.js
const b = require("./module-b");
console.log(b);
module.exports = "我是a模块";
// module-b.js
const a = require("./module-a");
console.log(a);
module.exports = "我是b模块";

commonjs 不会死循环,⽽是加载已经加载的部分结果,因为如果缓存中有,则使⽤缓存中的 exports 结果。初始化时默认会将当前模块也放⼊到缓存中。

4.exports 和 module.exports 的区别

module.exports 和 exports 引⽤的是同⼀个内存地址,但是导⼊模块时最终采⽤的是 module.exports 结果。

function require() {
  let exports = (module.exports = {});
  // 这⾥要注意,如果我们直接修改exports是⽆效的
  // exports = {}; // ⽆效
  // exports.xxx = {}; // 有效
  return module.exports;
}

同时我们还要注意 exports 和 module.exports 不能⼀起使⽤,否则使⽤ exports 导出的结果将会失效。

5.this 问题

Node 中的 this 指向的是 exports 对象,不是全局对象。⽬的是为了⽤户可以再 this 上挂载属性实现快速导出

6.思考

如果⽤户导出的是⼀个变量,后续变量被修改了,是否会影响导⼊的值。

// exportsA.js
let a = 0;
setInterval(() => {
  a++;
}, 1000);
module.exports = a; // 如果导出的是引⽤类型则会产⽣变化。
// useExportsA.js
setInterval(() => {
  let a = require("./exportsA");
  console.log(a); // require会缓存上次导⼊的结果。
}, 1000);