博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于CommonJS规范,简单实现NodeJs模块化
阅读量:5946 次
发布时间:2019-06-19

本文共 4503 字,大约阅读时间需要 15 分钟。

目录

CommonJS

CommonJS是一个模块化的规范,Nodejs的模块系统,就是参照CommonJS规范实现的。每个文件就是一个模块 ,每个模块都要自己的作用域。

特点

  1. 所有代码都有运行在模块作用域,不会污染全局作用域
  2. 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  3. 模块加载的顺序,按照其在代码中出现的顺序。

NodeJs 模块化简单实现

首先我们先了解一下模块化加载:

  1. 模块加载
  2. 模块解析文件名,解析出绝对路径来
  3. 我们引入时可以省略文件的后缀,有可能没有写后缀名, 会自动查找对应目录下 .js.json的文件
  4. 多次调用只走一次, 得到一个真实存在的文件路径,在缓存中看文件是否存在,不存在则创建模块,然后放入到缓存中,方便别人读取
  5. 读取文件内容。如果是 .js 文件,就把内容加一个闭包
  6. 运行JS脚本,将结果返回

0. 创建module构造函数

每个模块内部,都有一个 module 对象,代表当前模块。

查看 console.log(module) module都有哪些属性:

  • module.id 模块的识别符,通常是带有绝对路径的模块文件名
  • module.filename 模块的文件名,带有绝对路径
  • module.loaded 返回一个布尔值,表示模块是否加载
  • module.parent 返回一个对象,表示调用该模块的模块
  • module.children 返回一个数组,表示该模块要用到的其他模块
  • module.exports 表示模块对外输出的值,默认值为空对象
function Module(id) {    // 下面就简单定义两个常用的    this.id = id    this.exports = {}}复制代码

1. 模块加载

引入自己写的模块
// 我们自己写的文件模块 要写路径 ./ 或者../let hello = require('./1.NodeJS')console.log(hello) // first NodeJS复制代码
引入内置模块就直接写名称即可
//操作文件的模块let fs = require('fs')// 处理路径的模块let path = require('path')// 虚拟机模块,沙箱运行,防止变量污染let vm = require('vm')复制代码

2. 解析文件,返回绝对路径

Module._resolveFilename 方法实现的功能是判断用户引入的模块是否包含后缀名,如果没有后缀名会根据 Module._extensions 的键的顺序查找文件,直到找到后缀名对应的文件的绝对路径,优先查找 .js

// 将引入文件处理为绝对路径Module._resolveFilename = function (p) {  // 以js或者json结尾的  if ((/\.js$|\.json$/).test(p)) {    // __dirname当前文件所在的文件夹的绝对路径    // path.resolve方法就是帮我们解析出一个绝对路径出来    return path.resolve(__dirname, p);  } else {    // 没有后后缀  自动拼后缀     // Module._extensions 处理不同后缀的模块    let exts = Object.keys(Module._extensions);    let realPath; // 存放真实存在文件的绝对路径    for (let i = 0; i < exts.length; i++) {      // 依次匹配对应扩展名的绝对路径      let temp = path.resolve(__dirname, p + exts[i])      try {        // 通过fs的accessSync方法对路径进行查找,找不到对应文件直接报错         fs.accessSync(temp)        realPath = temp        break      } catch (e) {      }    }    if (!realPath) {      throw new Error('module not exists');    }    // 将存在绝对路径返回    return realPath  }}复制代码

Module._extensions 处理对应模块扩展名。这里我们只提 .js .json,对应模块的处理功能我们在后面来实现

Module._extensions = {    "js": function() {},    "json": function() {}}复制代码

3. 多次调用只一次

当用户重复加载一个已经加载过的模块,我们只有第一次是加载,然后放入缓存中,后面在加载时,直接返回缓存中即可

Module._cacheModule = { } 存放模块缓存

// 获取内存中的结果let cache = Module_cacheModule[filename]// 判断是否存在if (cache) {    // 如果存在直接将 exports对象返回    return cache.exports}// 如果不存在内存中,就创建模块,然后加入到内存中let module = new Module(filename)Module._cacheModule[filename] = module复制代码

4. 加载模块,对于不同类型的文件做不同的处理

根据前面我们说到的模块化,对于已经存放在内存中的我们直接返回就可以了,对于新添加的模块,我们该读取文件了, 根据传入的模块,尝试加载模块方法

// 根据传入的模块,尝试加载模块方法function tryModuleLoad(module) {  // 前面我们已经提到 module.id 为模块的识别符,通常是带有绝对路径的模块文件名  // path.extname 获取文件的扩展名  /* let ext = path.extname(module.id);  // 如果扩展名是js 调用js处理器 如果是json 调用json处理器  Module._extensions[ext](module); // exports 上就有了数组 */  let ext = path.extname(module.id);//扩展名  // 如果扩展名是js 调用js处理器 如果是json 调用json处理器  Module._extensions[ext](module); // exports 上就有了数组}复制代码

Module._extensions 处理对应后缀名模块。这里我们只提 .js .json

// 处理对应后缀名模块Module._extensions = {  ".js": function (module) {    // 对于js文件,读取内容    let content = fs.readFileSync(module.id, 'utf8')    // 给内容添加闭包, 后面实现    let funcStr = Module.wrap(content)    // vm沙箱运行, node内置模块,前面我们已经引入, 将我们js函数执行,将this指向 module.exports    vm.runInThisContext(funcStr).call(module.exports, module.exports, req, module)  },  ".json": function (module) {    // 对于json文件的处理就相对简单了,将读取出来的字符串转换未JSON对象就可以了    module.exports = JSON.parse(fs.readFileSync(module.id, 'utf8'))  }}复制代码

上面我们使用了 Module.wrap 方法,是帮助我们添加一个闭包,简单说就是我们在外面包了一个函数的前半段和后半段

// 存放闭包字符串Module.wrapper = [  "(function (exports, require, module, __filename, __dirname) {",  "})"]复制代码
// 将我们读到js的内容传入,组合成闭包字符串Module.wrap = function (script) {  return Module.wrapper[0] + script + Module.wrapper[1];}复制代码

5. 运行并返回结果

要运行,我们来看一下完整的模块加载代码

// 模块加载Module._load = function (f) {  // 相对路径,可能这个文件没有后缀,尝试加后缀  let fileName = Module._resolveFilename(f); // 获取到绝对路径  // 判断缓存中是否有该模块  if (Module._cacheModule[fileName]) {    return Module._cacheModule[fileName].exports  }  let module = new Module(fileName); // 没有就创建模块  Module._cacheModule[fileName] = module // 并将创建的模块添加到缓存  // 加载模块  tryModuleLoad(module)  return module.exports}复制代码

到这里,一个简单的module就实现了,让我们测试一下吧

// 测试代码function req(p) {  return Module._load(p); // 加载模块}// 第一次没有缓存,创建module,并添加到缓存中let str = req('./1.NodeJS');// 第二次就是返回的缓存中的let str1 = req('./1.NodeJS.js');console.log(str) // first NodeJSconsole.log(str1) // first NodeJS复制代码

附源码

重要为了方便大家了解、查看、调试代码,完整的源码参见

总结

本篇文章是基于CommonJS规范,实现了一个简单的NodeJS模块化,主要目的在于理解 NodeJS 模块化的实现思路,希望对大家了解模块化起到一定的作用。

作者:香香

将来的你,一定会感谢现在拼命努力的自己!

Node基础系列文章

转载地址:http://zzkxx.baihongyu.com/

你可能感兴趣的文章
js /jquery停止事件冒泡和阻止浏览器默认事件
查看>>
线程的一些解释
查看>>
mysql+keepalived搭建高可用环境
查看>>
java实现插入排序
查看>>
Linux下php连接sql server 2008
查看>>
python字符串格式化
查看>>
关于html和CSS的几个基本知识点
查看>>
Fiddler (三) Composer创建和发送HTTP Request
查看>>
C语言 多维数组和指针
查看>>
DotNetBar的使用—(界面风格)
查看>>
2.3系列系统中不支持SimpleDateFormat作字段被序列化
查看>>
DJANGO MODEL FORMSETS IN DETAIL AND THEIR ADVANCED USAGE
查看>>
ADO.NET复习——自己编写SqlHelper类
查看>>
库函数strlen源码重现及注意问题
查看>>
《实例化需求》读书笔记
查看>>
常用Java8语法小结
查看>>
ZJOI2019 Day2 游记
查看>>
ccf题库中2015年12月2号消除类游戏
查看>>
WinForm窗体间如何传值
查看>>
Ado.Net 连接数据库
查看>>