CommonJS规范概述
- 一个文件就是一个模块,拥有单独的作用域
- 普通方式定义的变量,函数,对象都属于该模块内的私有属性
- 通过
require
来加载其他模块通过module.exports
导出的内容 - 通过
exports
和module.exports
来导出模块中要暴露的内容
简单使用一下CommonJS
- A文件
let str = 'hello world';
module.exports = str;
复制代码
- B文件
// 通过require引入A模块中使用exports或module.exports的内容
let a = require('./A');
console.log(a); // => 'hello world'
复制代码
CommonJS的简易实现
先梳理一下流程:
- Module._load 加载模块require引入的模块
- Module._resolveFilename 通过模块名 解析出一个绝对路径
- Module._cache 如果之前require过这个模块 就存到缓存中
- new Module 如果缓存中没有 就创建模块 每个模块都有一个exports属性
- tryModuleLoad() 尝试加载模块
- Module._extensions 依据模块的扩展名执行对应的方法
准备工作
先引入我们需要的
node
内置模块
// path是专门用来处理路径的模块
let path = require('path');
// fs是专门用来操作文件的模块
let fs = require('fs');
// 我们需要用vm来提供一个将字符串当成变量执行的沙箱环境
let vm = require('vm); 复制代码
创建一个
Module
类,我们每次引入都会new
一个Module
实例,引入的其实是这个实例上的exports
属性
function Module(p) {
// 将当前模块的绝对路径当做这个模块的标识符存在实例身上
this.id = p;
// exports存的是其他模块require引入的内容
this.exports = {};
}
复制代码
一些静态属性
// 缓存 我们将每个模块的绝对路径当做key,每次引入都判断这个key存不存在,如果存在就直接返回之前存过的
Module._cache = {}
// 我们根据不同的后缀名执行不同的加载方法
Module._extensions = {
'.js'() {
},
'.json'() {
}
}
// 引入的模块是js文件时需要套一个闭包
Module.wrapper = [
'(function(exports, req, module){'
'})'
];
复制代码
创建一个引入模块的方法,为了与require
区别开我们叫req
当我们引入模块的时候,会调用
Module._load
方法来加载模块
function req(p) {
return Module._load(p)
}
复制代码
Module._load 加载模块
我们主要的操作都在
Module._load
方法中执行,在Module._load
方法中,首先会把引入的模块通过Module._resolveFilename
方法解析出一个绝对路径
Module._load = function(p) {
let filename = Module._resolveFilename(p);
}
复制代码
Module._resolveFilename 解析文件名 返回一个绝对路径
- 我们首先判断模块有没有后缀名
- 如果有后缀名,判断这个文件存在还是不存在,如果存在就返回一个绝对路径,不存在就抛出一个异常
- 如果没有后缀名,我们依次拼接
Module._extensions
中的key
,如果存在 就返回
// fs.accessSync(path); 判断文件存不存在,如果存在一切正常,如果不存在就会报错,所以用try catch包起来
Module._resolveFilename = function(p) {
let realPath;
// 如果有js或json后缀名
if (/\.js$|\.json$/.test(p)) {
try {
realPath = path.resolve(__dirname, p);
fs.accessSync(realPath);
return realPath;
} cache(e) {
throw new Error('Module not fount')
}
} else {
let exts = Object.keys(Module._extensions);
for (let i = 0; i < exts.length; i++) {
// 将后缀名拼上
let temp = path.resolve(__dirname, p + exts[i]);
// 判断文件存不存在
try {
fs.accessSync(temp);
// 如果存在就保存一下值 并跳出循环
realPath = temp;
break;
} catch(e) {
}
}
if (realPath) {
return realPath;
}
throw new Error('Module not found')
}
}
复制代码
Module._cache 判断有没有加载过
我们之前说过,会把这个模块的绝对路径当做key存到缓存中,现在已经解析出一个绝对路径了,接下来只需要判断缓存中有没有
Module._load = function(p) {
let filename = Module._resolveFilename(p);
// 获取缓存
let cache = Module._cache[filename];
// 如果有缓存,返回缓存中导出的内容
if (cache) {
return cache.exports;
}
// 如果之前没有缓存 代表第一次引入
// 每个实例上都有一个私有属性id,存的是自己的绝对路径(唯一标识),还有一个exports属性 存的的引入的模块导出的内容
let module = new Module(filename);
// 加载模块,我们将当前实例传过去
tryLoadModule(module);
}
复制代码
tryLoadModule() 加载模块
在这个方法中我们只需要通过模块的后缀名去执行
Module._extensions
中对应的加载方法
// fs.extname() 获取文件后缀名
function tryLoadModule(module) {
// module.id 存的是当前模块的绝对路径
let ext = fs.extname(module.id);
// 执行Module._extensions中对应的加载方法
Module._extensions[ext](module);
}
复制代码
Module._extensions 真正的读取文件方法
// fs.readFileSync() 读取文件内容
Module._extensions = {
'.json'(module) {
// 如果是json文件我们之间读取后赋值给module的exports属性就好了
module.exports = fs.readFileSync(module.id, 'utf8');
},
'.js'(module) {
// 如果是js文件的话,我们要给他套一个闭包,实现模块化,还记得上面的Module._wrapper属性吗
let fnStr = Module._wrapper[0] + fs.readFileSync(module.id, 'utf8') + Module._wrapper[1];
console.log(fnStr);
// 打印出来的是
// '(function(exports, req, module){'
// 通过fs.readFileSync 读取出的内容
// '})'
// 打印出来的是一个字符串,那么怎么让这个字符串执行呢
// 1. eval() 2. new Function() 3. vm模块
// 我们选择使用vm模块的runInThisContext方法,因为这样不依赖上下文环境
let fn = vm.runInThisContext(fnStr);
// 现在fn就是一个函数了,不是一个字符串了,可以直接执行
fn.call(module.exports, module.exports, req, module);
// fn的this是module.exports,所以我们再node中打印this,有时是一个空对象,我们将上个模块导出的内容给到exports属性了,我们最后只需要 return module.exports这个属性就可以了
}
}
复制代码
结束
Module._load = function(p) {
let filename = Module._resolveFilename(p);
let cache = Module._cache[filename];
if (cache) {
return cache.exports;
}
let module = new Module(filename);
tryLoadModule(module);
// 返回exports中的内容
return module.exports;
}
function req(p) {
// Module._load() 返回的就是上个模块导出的内容,我们在直接return就可以了
return Module._load(p)
}
复制代码
完结撒花
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/101420.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...