Node模块化
TIP
Node目前采用的是commonjs规范编写的模块化
1、commonjs规范
- 每个js文件都是一个模块
- 模块的导出 module.exports
- 模块的导入require
2、模块的分类
- 核心模块/内置模块,不需要安装,直接引入使用,引入不需要加入相对路径和绝对路径,比如:fs http path
- 自定义模块,需要通过绝对路径或者相对路径进行引入
- 第三方模块,需要先安装,再引入使用
3、require模块实现代码
// 1、建一个文件a.js
console.log(1);
module.exports = 'hello';
// 2、编写模块化代码
const path = require('path');
const fs = require('fs');
const vm = require('vm');
// 定义模块函数
function Module(fileName) {
this.id = fileName; // 文件名
this.exports = {}; // 导出的结果
this.path = path.dirname(fileName); // 父目录
}
// 给字符串包裹函数
Module.wrapper = (content) =>{
// 假如说我把变量挂载在了global newFunction 是获取不到的
return `(function(exports,require,module,__filename,__dirname){${content}})`
}
// 模块静态方法
Module._extensions = Object.create(null);
// 模块缓存
Module._cache = Object.create(null);
// 策略一:模块加载js文件
Module._extensions['.js'] = function(module) {
// 1、读取文件
let content = fs.readFileSync(module.id, 'utf8');
// 2、给字符串包裹函数
let str = Module.wrapper(content);
// 3、将字符串变为函数
let fn = vm.runInThisContext(str);
// 4、函数执行,赋值结果
let exports = module.exports;
fn.call(exports, exports, myRequire, module, module.id, module.path);
// 模块中的this是module.exports 不是 module
// 参数:exports require module __dirname __filename
// 这句代码执行后 会做module.exports = 'hello'
}
// 策略二:模块加载json文件
Module._extensions['.json'] = function(module) {
let content = fs.readFileSync(module.id, 'utf8');
// 手动将读取的json字符串转化为json对象
module.exports = JSON.parse(content);
}
// 静态方法: 解析查找文件路径
Module._resolveFilename = function(fileName) {
// 1、解析文件路径
let filePath = path.resolve(__dirname, fileName);
// 2、判断文件路径是否存在,存在就返回,不存在继续解析后缀加载
let isExists = fs.existsSync(filePath);
if (isExists) return filePath;
// 3、解析后缀(.js 和 .json),继续加载
let keys = Reflect.ownKeys(Module._extensions);
for (let i = 0; i < keys.length; i++) {
let newFile = filePath + keys[i];
if (fs.existsSync(newFile)) return newFile;
}
throw new Error('module not found');
}
/**
* 原型定义加载方法:
* 加载时 需要获取当前文件的后缀名 ,根据后缀名采用不同的策略进行加载
*/
Module.prototype.load = function() {
let extensions = path.extname(this.id);
Module._extensions[extensions](this);
}
// 自定义require方法
function myRequire(fileName) {
// 1、解析当前的文件名
fileName = Module._resolveFilename(fileName);
// 判断是否有缓存,如果有直接返回exports
if(Module._cache[fileName]){
return Module._cache[fileName].exports;
}
// 2、创建模块
let module = new Module(fileName);
// 给模块添加缓存
Module._cache[fileName] = module;
// 3、加载模块
module.load();
// 4、返回结果
return module.exports;
}
// 3、引入测试
let r = myRequire('./a');
myRequire('./a');
myRequire('./a');
myRequire('./a');
myRequire('./a');
console.log(r);
4、总结
- 查找顺序
- 先查找当前文件夹下的js文件
- 如果JS查找不到,就查找json文件
- 如果json查找不到,就查找package.json中main字段,找到对应结果
- 如果main查找不到,就查找index.js
- require加载方式为同步加载,需要加载完成才能执行后续操作
- 正确用法
- exports.a
- module.exports.a
- module.exports
- global