# Node模块化

TIP

Node目前采用的是commonjs规范编写的模块化

# 1、commonjs规范

  1. 每个js文件都是一个模块
  2. 模块的导出 module.exports
  3. 模块的导入require

# 2、模块的分类

  1. 核心模块/内置模块,不需要安装,直接引入使用,引入不需要加入相对路径和绝对路径,比如:fs http path
  2. 自定义模块,需要通过绝对路径或者相对路径进行引入
  3. 第三方模块,需要先安装,再引入使用

# 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、总结

  • 查找顺序
    1. 先查找当前文件夹下的js文件
    2. 如果JS查找不到,就查找json文件
    3. 如果json查找不到,就查找package.json中main字段,找到对应结果
    4. 如果main查找不到,就查找index.js
  • require加载方式为同步加载,需要加载完成才能执行后续操作
  • 正确用法
    1. exports.a
    2. module.exports.a
    3. module.exports
    4. global