Nodejs 基础篇

146 阅读2分钟

CommonJs的实现原理

1.在Node中任何js、json文件都是一个模块

2.Node模块化作用域隔离使用的是函数的作用域实现的,每个文件内部的代码会被Node包装到一个函数内部等到被require的时候执行函数,返回模块的导出内容并缓存模块的执行结果,下次require引用直接从缓存中获取,由于require的加载是同步的所以会阻塞代码执行

3.模块化解决的主要问题

1)抽离公共方法,提高代码复用率

2)代码按照模块解耦,作用域之间相互隔离互相不影响,实现高内聚低耦合

3)按需加载提升效率,只有执行require才会运行模块的代码

4.可以通过require('module')获取node中全局的模块构造函数,系统加载的模块会统一放入到这个全局的模块构造函数的静态属性_cache对象中,这样一来只需要加载一次模块,后续如果要使用相同的模块只需要从这个模块构造函数的静态属性_cache中获取即可

const globalModule = require('module')
console.log(globalModule);

image.png

5.使用js代码模拟Node读取文件代码内容后进行的包装增强过程

1)main.js

const fs = require("fs");
const path = require("path");
const vm = require("vm");

// 全局的模块类
class MyModule {
  constructor(id) {
    this.id = id;
    this.export = {};
  }
  
  // 设置静态属性,相当于设置类自己的属性,生成的对象没有该属性,只能通过类访问
  static wrapper = [
    "(function(myExports, myModule, MyRequire, __dirname, __filename){",
    "})",
  ];

  static execCode(myModule) {
    const content = fs.readFileSync(myModule.id, "utf8");
    const fnStr = MyModule.wrapper[0] + content + MyModule.wrapper[1];
    const fn = vm.runInThisContext(fnStr);
    fn.call(myModule.exports, myModule.exports, myModule, myRequire, __filename, __dirname);
  }
  static _cache = {};
}

const myRequire = (absPathname) => {
  absPathname = path.resolve(__dirname, absPathname);
  if (MyModule._cache[absPathname]) {
    return MyModule._cache[absPathname].exports;
  }
  const myModule = new MyModule(absPathname);
  MyModule._cache[absPathname] = myModule; // 模块的缓存应该在MyModule.execCode(myModule)前面,这样可以防止循环依赖造成的死循环
  MyModule.execCode(myModule) 
  return myModule.export;
};
const exportData = myRequire("./module.js");
console.log(exportData); // 打印结果:{ name: "张三" }

2)module.js

myModule.export = {
  name: '张三'
}

6.参考文献

1)前端技术探秘 - Nodejs 的 CommonJS 规范实现原理 | 京东物流技术团队 - 知乎 (zhihu.com)

2)JavaScript模块化开发的前世今生 - 果冻想 - SegmentFault 思否

3.CommonJS 和 ES6 Module 究竟有什么区别? - 掘金 (juejin.cn)

文件模块

1.node文件模块简介: