# 你不知道的require
# require 内部流程
TIP
我们先大致了解一下 require()引入一个 .js
或者 .json
文件时候,内部大致是怎么处理的。
let name = require('./a')
console.log(name)
上述代码引入 a.js
文件时候,内部大致发生的流程如下:
1、将 ./a
转化为绝对路径,并且补充后缀名(c:\Users\chenying\Desktop\code\a.js
)
2、根据绝对路径判断缓存中是否存在缓存的文件,如果存在则取缓存,不存在则继续
3、创建 Module
实例 module
,将绝对路径传入
4、取得绝对路径的后缀名,根据后缀名(.js
)调用对应的处理函数
5、读 .js
和 .json
文件思路大同小异,通过fs.readFileSync()读取文件内容
6、对读到的 .js
文件的内容外层包裹一个函数,并且将字符串转成函数执行
7、对读到的 .json
文件的内容,转为对象,并且赋值给module.exports
TIP
我们再来看下源码中对以下几个疑惑点是怎么进行处理的
# Module
类
function Module(id) {
this.id = id;
this.exports = {};
// ..
}
在源码中我们发现 Module
上挂载很多,但是我们真正需要接触到的分别是 id
和 exports
,他们分别是绝对路径,和默认导出的空对象
# 取得绝对路径的后缀名,根据后缀名(.js
)调用对应的处理函数
Module._extensions = Object.create(null); //创建一个对象
Module._extensions['.js'] = function(module, filename) {}; // 在对象上挂载 `.js`的处理函数
Module._extensions['.json'] = function(module, filename) {}; // 在对象上挂载 `.json`的处理函数
Module._extensions[extension](this, filename); // 根据绝对路径的后缀名,调用挂载在对象上相应的方法
用 Object.create(null)
是不想有原型上的属性
# 通过fs对文件进行读取
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8'); // 读取文件内容
module._compile(stripBOM(content), filename); // 对读取的内容包裹一层函数,并且转为函数自执行,让用户自己把想要导出的内容挂载到 `module.exports` 上面去
};
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module.exports = JSON.parse(stripBOM(content)); // 读到的json文件直接挂载到 `module.exports` 上
};
# 对读到的 .js 文件的内容外层包裹一个函数,并且将字符串转成函数执行
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
const wrapper = Module.wrap(content);
compiledWrapper = vm.runInThisContext(wrapper)
compiledWrapper.call(this.exports, this.exports, require, this,filename, dirname)
# mini 版require的实现
TIP
我们现在根据步骤结合node来实现一款require()加深理解
# 将传入的路径转为绝对路径,并且创建 module
实例,将module.exports导出
let path = require('path');
let fs = require('fs');
let vm = require('vm');
function Module(id){
this.id = id;
this.exports = {}
}
function myRequire(filePath){
let absPath = path.resolve(__dirname,filePath); // 把当前的filePath变成一个绝对路径
let module = new Module(absPath);
module.load(); // 加载模块
return module.exports
}
let r = myRequire('./a.js');
console.log(r);
# 取得绝对路径的后缀名,根据后缀名(.js)调用对应的处理函数
Module._extensions = {};
Module.prototype.load = function(){
let ext = path.extname(this.id); // 取出当前实例上挂载的绝对路径,获取后缀名
Module._extensions[ext](this) // 根据后缀名调用对应的处理函数
}
# Module._extensions
上补充对应的处理函数
let wrapper = [
'(function(exports,module,require,__dirname,__filename){'
,
'})'
]
Module._extensions['.js'] = function(module){
let script = fs.readFileSync(module.id,'utf8'); //读取文件
let functStr = wrapper[0] + script + wrapper[1]; // 字符串包裹
let fn = vm.runInThisContext(functStr); //将字符串转为函数
fn.call(module.exports,module.exports,module,myRequire);//执行函数
}
Module._extensions['.json']= function(module){
let script = fs.readFileSync(module.id,'utf8');//读取文件
module.exports = JSON.parse(script); // 将读取到的json挂载到 `module.exports`
}
TIP
目前已经根据源码思路来实现一款mini版require(),但是还有类似于实现缓存、自动补全后缀名等功能就不贴代码了,可以看下方的源码,欢迎star
# 源码
← 深拷贝和浅拷贝 events-观察者模式 →