乐趣区

关于node.js:手写nodejs原生require方法

前言

这两天在学习 nodejs 相干的货色,在 b 站上看到一个 up 主分享的视频还挺不错的,于是跟着敲了一下。

实现

第一步:定义 myRequire 办法

function myRequire(filename) {
  // 获取绝对路径
  const mPath = Module._resolveFilename(filename);

  // 缓存优先
  const cacheModule = Module._cache[mPath];
  if (cacheModule) return cacheModule.exports;

  // 创立空对象加载指标模块
  const module = new Module(mPath);

  // 缓存已加载模块
  Module._cache[mPath] = module;

  // 执行加载
  module.load();

  return module.exports;
}

第二步:定义 Module 类办法

function Module(id) {
  // 模块 id 理论就是绝对路径
  this.id = id;
  this.exports = {};}

第三步:实现 Module._resolveFilename 获取文件绝对路径的办法,只对以后的目录做了简略的查找、判断,理论会向上一层层查找模块,此处省略。当找不到时会尝试对文件名拼接文件后缀,此处只提供了 js 和 json 的办法,如果还是找不到便抛出谬误。

Module._resolveFilename = function (filename) {
  // 拼接当前目录和文件名
  const mPath = path.join(__dirname, filename);
  // 判断此文件是否存在,存在之后返回
  if (fs.existsSync(mPath)) {return mPath;}
  // 如果不存在,尝试拼接后缀
  const suffixs = Object.keys(Module._extensions);
  for (let i = 0; i < suffixs.length; i++) {const _mPath = mPath + suffixs[i];
    if (fs.existsSync(_mPath)) {return _mPath;}
  }
  // 找不到抛错
  console.log(new Error(`${filename} is no exits`));
};

第四步:当获取到文件的绝对路径后,先去缓存中查找模块,如果有间接返回。反之创立模块对象,并向缓存中增加此模块,最初执行模块加载,也是外围的编译执行模块。

模块加载依据不同的文件类型去执行对应的编译办法,此处只做了 js 和 json 的编译办法。

js 文件读取内容之后,将 js 字符串封装成一个办法,并向其提供对应的模块可应用默认变量,最初应用 vm.runInThisContext 办法去运行 js 代码。

json 文件就更简略了,间接读取文件应用 JSON.parse 格式化之后赋值给 module.exports 即可

Module._wrapeer = ["(function(exports, module, require, __dirname, __filename){",
  "})",
];

Module._extensions = {".js"(_module) {
    // 读取文件内容
    const content = fs.readFileSync(this.id, "utf-8");
    // 将内容字符串封装成一个函数,并传入 exports、module、require、__dirname、__filename 默认能够应用的变量
    const contentFn = Module._wrapeer[0] + content + Module._wrapeer[1];
    // 调用 node.js 自带的 vm.runInThisContext 办法运行字符串的 js 代码
    const fn = vm.runInThisContext(contentFn);
    // 创立模块须要的默认变量
    const exports = this.exports;
    const module = this;
    const dirname = path.dirname(this.id);
    const filename = this.id;
    // 执行模块
    fn.call(exports, exports, module, myRequire, dirname, filename);
  },
  ".json"(_module) {
    // JSON.parse 格式化读取内容并设置给_module.exports 即可
    const content = JSON.parse(fs.readFileSync(_module.id, "utf-8"));
    _module.exports = content;
  },
}

Module.prototype.load = function () {
  // 依据文件类型去执行对应的编译办法
  const extname = path.extname(this.id);
  Module._extensions[extname](this);
};

最初,返回 module.exports 即是导出的内容。up 主叫 five 爱前端,感兴趣的本人去搜吧。

退出移动版