commonjs

Commonjs

什么是 CommonJs

  1. CommonJs 是 js 模块化的社区规范

模块化产生的原因

  1. 随着前端页面复杂度的提升,依赖的第三方库的增加,导致的 js 依赖混乱,全局变量的污染,和命名冲突
  2. 单个 js 文件内容太多,导致了维护困难,拆分成为多个文件又会发生第一点描述的问题
  3. v8 引擎的出现,让 js 有了媲美编译型语言的运行速度,大大激励了前端开发者

CommonJS 的使用环境

  1. nodejs 实现了 CommonJS 模块化规范

CommonJs 有哪些规定

  1. 每一个文件就是一个模块
  2. 模块中的变量和函数不会污染全局(解决了全局污染和命名冲突)
  3. 提供给外部使用的内容需要导出
  4. 使用其他模块的内容需要导入 (模块的导入和导出共同解决了 js 依赖混乱的问题)
  5. 模块不会重复加载,模块第一次导入后会将第一次导入的结果缓存,下次导入时,直接使用缓存的结构
  6. 省略一些细碎的内容在下面代码中提及.....

commonJS 语法

  1. 导入
//这是导入一个模块,module.js;commonjs中规定require导入模块时可以省略.js后缀
const module1 = require("./module1");
//如果没有寻找到dir.js文件,而发现了dir路径,则寻找dir路径下package.json 的main属性指定的文件
//如果package.json未指定路径,则触发默认规则 依次查找查找 index.js index.json
const module2 = require("./dir");
//如果require不是相对路径,则会去node_module中寻找该模块,重复module1 和module2 的步骤
//如果没有node_modules 或node_modules 中不存在模块则继续向上级目录寻找node_modules,直到根目录
const module3 = require("module3");
  1. 导出
module.exports = {
  //这里输入导出的内容
};

//这也是导出
exports.a = "a";

//注意 module.exports导出和exports[属性名]导出不可共存
//module.exports会覆盖掉exports导出的内容

简易实现类 nodejs 模块化环境

const fs = require("fs");
const Path = require("path");
const vm = require("vm");
const ModuleStack = [];

function isRootDirectory(path) {
  // Windows 根路径
  const windowsRootDirectory = /^[a-zA-Z]:\\$/;
  // Unix/Linux 根路径/
  const unixRootDirectory = /^\//;

  return windowsRootDirectory.test(path) || unixRootDirectory.test(path);
}

function isRelativeDirectory(path) {
  //匹配 ../ 或者 ./开头的路径
  const relativeDirectory = /^(\.\.\/|\.\/).+/;
  return relativeDirectory.test(path);
}
// 计算node_modules路径
let computePaths = (dirname) => {
  let paths = [];
  let path = dirname;
  let node_modules = "./node_modules";
  while (
    !isRootDirectory(path) ||
    !paths.includes(Path.resolve(path, node_modules))
  ) {
    paths.push(Path.resolve(path, node_modules));
    path = Path.resolve(path, "../");
  }
  return paths;
};

function myRequire(path) {
  let truelyPath;
  if (isRelativeDirectory(path)) {
    // 获取真实路径
    truelyPath = Path.resolve(__dirname, path);
  } else {
    //获取可能的node_modules路径
    let paths = computePaths(__dirname);
    for (const item of paths) {
      truelyPath = Path.resolve(item, path);
      if (fs.existsSync(truelyPath)) {
        break;
      }
    }
    if (!truelyPath) {
      throw new Error("Can't find module " + path);
    }
  }
  // 如果缓存中有,直接返回
  if (myRequire.cache[truelyPath]) {
    return myRequire.cache[truelyPath].exports;
  }
  // 读取文件内容
  const content = fs.readFileSync(path, "utf-8");
  // 包装代码
  const wrapper = [
    "(function (exports, require, module, __filename, __dirname) { \n",
    "\n})",
  ];
  // 拼接代码
  const wrapperContent = wrapper[0] + content + wrapper[1];

  // 获取文件路径和文件名
  let dirname = Path.dirname(truelyPath);
  let filename = truelyPath;
  let parentModule =
    ModuleStack.length > 0 ? ModuleStack[ModuleStack.length - 1] : null;
  // 模块对象
  const Module = {
    id: Object.keys(myRequire.cache).length > 0 ? filename : ".",
    path: dirname,
    exports: {},
    parent: parentModule,
    filename: filename,
    loaded: false,
    children: [],
    paths: computePaths(dirname),
  };
  if (parentModule) {
    parentModule.children.push(Module);
  }
  //模块入栈
  ModuleStack.push(Module);
  // 需要运行的函数
  const moduleScope = vm.runInThisContext(wrapperContent);
  // 运行代码
  moduleScope.call(
    Module.exports,
    Module.exports,
    myRequire,
    Module,
    filename,
    dirname
  );
  // 标记模块已加载
  Module.loaded = true;
  //模块出栈
  ModuleStack.pop();
  // 缓存模块
  myRequire.cache[truelyPath] = Module;
  return Module.exports;
}

myRequire.cache = Object.create(null);

模块化的意义

  1. 解决在模块化出现之前的js依赖混乱,全局污染命名冲突的问题
  2. 模块化的出现让js代码可以拆分为多个模块共同协作,单个js文件过长的问题,降低了维护难度。
  3. 模块化的出现让js开发大型项目出现了可能

ps:当前内容为学习commonjs理解,内容正确性请谨慎甄别。

posted @ 2024-04-17 21:59  jiang_xin_yu  阅读(119)  评论(1编辑  收藏  举报