commonJs是前端模块化规范的其中一种,主要使用在node.js。javascript
每一个文件都是一个模块,有本身的做用域。在一个文件里面定义的变量、函数、类,都是私有的,对其余文件不可见。html
定义一个a.js的文件前端
const fn = function () {
console.log('Hello');
}
module.exports = fn;
复制代码
再定义b.js文件,并在其中引入a.jsjava
const fn = require('./b');
fn(); // Hello
复制代码
b中调用a中的fn方法,打印出‘Hello’。node
const req = function(id) {...} // 取名req与系统默认的require做区分
const fn = req('./a');
复制代码
function Module(id) {
this.id = id;
this.exports = {};
}
复制代码
commomJs能够引入.js、.json、.node、.mjs的文件,因为篇幅有限,这里只定义了.js和.json的处理方法。json
Module.extensions = {};
Module.extensions['.js'] = function (module) {...};
Module.extensions['.json'] = function (module) {...};
复制代码
Module.getPath = function(id) {
const absPath = path.resolve(id); // 得到绝对路径
if (fs.existsSync(absPath)) { // 若输入的路径已包含后缀,能够直接找到
return absPath;
}
const extensions = Object.keys(Module.extensions);
for (let i = 0;i < extensions.length; i++) {
const ext = `${absPath}${extensions[i]}`;
if (fs.existsSync(ext)) {
return ext;
}
}
throw new Error('The file do not exist'); // 加上后缀还没找到,抛出错误
}
复制代码
const req = function(id) {
const ext = Module.getPath(id); // 得到完整路径
const myModule = new Module(ext); // 实例化一个模块
const extName = path.extname(ext); // 得到文件的后缀
const result = Module.extensions[extName](myModule); // 执行对应后缀方法
return result;
}
复制代码
核心逻辑在于用fs文件系统读取到js文件中的内容,而后再将其封装成方法(为了保证私有做用域,以及将exports的值赋到咱们本身定义的module中)。api
let script = fs.readFileSync(module.id, 'utf8');
const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
复制代码
但此时得到的wrapper依旧是字符串,咱们须要将字符串转换成能执行的函数方法,此时通常会想到eval。咱们这里使用更高级的vm,具体使用方法参考vm官方文档。缓存
let script = fs.readFileSync(module.id, 'utf8');
const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
const fn = vm.runInThisContext(wrapper); // vm.runInThisContext返回封装的那个方法
复制代码
完整js处理实现bash
Module.extensions['.js'] = function (module) {
let script = fs.readFileSync(module.id, 'utf8');
const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
const fn = vm.runInThisContext(wrapper);
fn(module.exports, req, module, __dirname, __filename);
return module.exports;
};
复制代码
json的处理会简单不少,把文件读取到后,直接JSON.parse返回对象便可。app
Module.extensions['.json'] = function (module) {
let jsonContent = fs.readFileSync(module.id, 'utf8');
return JSON.parse(jsonContent);
};
复制代码
每次读取文件以后,下一次读取统一文件直接使用缓存便可。定义cache对象:
Module.cache = {};
复制代码
在req方法内,若是缓存内有数据直接返回缓存数据,而且在拿到新的模块数据后要将数据计入缓存中。完善后的req方法:
const req = (id) => {
const ext = Module.getPath(id);
if (Module.cache[ext]) { // 查询缓存
return Module.cache[ext]; // 查询到缓存,使用缓存
}
const myModule = new Module(ext);
// 对应后缀方法执行
const result = Module.extensions[path.extname(ext)](myModule);
Module.cache[ext] = myModule; // 计入缓存
return result;
};
复制代码
const path = require('path');
const fs = require('fs');
const vm = require('vm');
// Module处理
function Module(id) {
this.id = id;
this.exports = {};
};
// 缓存
Module.cache = {};
// 不一样后缀类型处理
Module.extensions = {};
Module.extensions['.js'] = function (module) {
let script = fs.readFileSync(module.id, 'utf8');
const wrapper = `(function (exports, require, module, __dirname, __filename) {${script}})`;
const fn = vm.runInThisContext(wrapper);
fn(module.exports, req, module, __dirname, __filename);
return module.exports;
};
Module.extensions['.json'] = function (module) {
let jsonContent = fs.readFileSync(module.id, 'utf8');
return JSON.parse(jsonContent);
};
Module.getPath = function (id) {
const absPath = path.resolve(id);
if (fs.existsSync(absPath)) {
return absPath;
}
const extensions = Object.keys(Module.extensions);
for (let i = 0; i < extensions.length; i++) {
const extPath = `${absPath}${extensions[i]}`;
if (fs.existsSync(extPath)) {
return extPath;
}
}
throw new Error('The file do not exist');
}
const req = (id) => {
const ext = Module.getPath(id);
if (Module.cache[ext]) {
return Module.cache[ext];
}
const myModule = new Module(ext);
// 对应后缀方法执行
const result = Module.extensions[path.extname(ext)](myModule);
Module.cache[ext] = myModule;
return result;
};
//如下是使用req的代码块
const func = req('./b');
func(); // Hello
复制代码