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