node中的模块化是基于commonJS来实现的,commonJS规范给javaScript制定了一个美好愿景,希望js能够在任何地方运行。它是commonJs的最佳实践者
模块使用规范
commonJs中模块主要分为模块引用,模块定义,模块标识三部分。
1)模块定义引用的话就是我们通过require关键字去引入(require是定义在module原型上的一个方法)
例如:我们定义一个构造函数将其暴露出去
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () { console.log(this.name) }
}
module.exports = Person
然后在另外一个文件加载
const Person = require('./test')
console.log(Person)
2)模块的定义
每个文件都相当于一个独立的模块,提供require引入外部模块,对应引入,导出提供了exports对象用于导出的唯一出口,它是modules上的属性,我们将需要导出的内容挂载在exports对象上就可以。
exports.Person = function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () { console.log(this.name) }
}
3) 模块的标识
这个标识就是require后面传入的文件路径
const Person = require('./test')
node中模块化的实现
我们在加载的时候传入模块的路径,这个时候会先解析传入的路径,得到绝对路径并且判断这个路径是否存在,如果不存在则提示报错
Module._resolveFilename = function(id) {
let filePath = path.resolve(__dirname, id); // 应该看下这个文件路径是否存在,如果不存在尝试添加后缀
let isExsits = fs.existsSync(filePath);
if (isExsits) return filePath; // 文件存在直接返回
let keys = Object.keys(Module._extensions); // [.js,.json]
for (let i = 0; i < keys.length; i++) {
let newFilePath = filePath + keys[i];
if (fs.existsSync(newFilePath)) return newFilePath
}
throw new Error('模块文件不存在')
}
当我们得到路径后,我们优先判断是否之前加载过,加载过的话从缓存中去取(获取的其实就是模块里面的exports),如果是新加载的模块,那么我们就去new一个module,并且存入缓存,
if(Module._cache[filename]){
return Module._cache[filename].exports; // 如果有缓存直接将上次缓存的结果返回即可
}
let module = new Module(filename);
Module._cache[filename] = module;
然后我们得到文件的后缀名,去执行不同的加载逻辑,内部使用了fs模块的readFileSync去读取模块内容
let extname = path.extname(this.id);
// 内部可能有n种解析规则
Module.analysis = {
'.js'(module) {
let script = fs.readFileSync(module.id, 'utf8'); // 读取文件的内容
let code = `(function (exports, require, module, __filename, __dirname) {
${script}
})`;
let func = vm.runInThisContext(code);
let exports = module.exports;
let thisValue = exports
let dirname = path.dirname(module.id);
func.call(thisValue,exports,req,module,module.id,dirname);
},
'.json'(module) {
let script = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(script)
} // 根据不同的后缀 定义解析规则}
总的来说就是我们在使用require加载模块的时候,会先解析出路径,然后判断是否缓存过,如果已经缓存,直接给出上一次的值,第一次加载则去new module,并且根据不同的文件后缀名执行不同的加载逻辑,require方法就是获取的就是module.exports属性