直接new req,我们在调用的时候会传入一个路径,这个路径就是文件名,我们就叫它filename,之后就调用了一个方法叫Module._load,接着又调用了一个方法 Module._resolveFilename
function Module (id) {
this.id = id
this.exports = {}
}
Module._resolveFilename = function (id) {
}
function req (filename) {
filename = Module._resolveFilename(filename)
}
我们需要用到三个模块 fs、path、vm
const fs = require('fs')
const path = require('path')
const vm = require('vm')
我们要判断这个路径是否存在
Module._resolveFilename = function (id) {
let filePath = path.resolve(__dirname, id)
let isExists = fs.existsSync(filePath)
if (isExists) return filePath
}
接着我们要尝试添加后缀,我们就用.js、.json来做例子
我们在filePath的基础上加keys[i]就等到了一个新的路径newPath
我们就判断newPath是否存在,如果存在就直接返回路径,不存在接着去添加,可是找了一圈没找着的话,就直接抛错
Module._extensions = {
'.js' () {},
'.json' () {},
}
Module._resolveFilename = function (id) {
let filePath = path.resolve(__dirname, id)
let isExists = fs.existsSync(filePath)
if (isExists) return filePath
// 尝试添加后缀
let keys = Reflect.ownKeys(Module._extensions)
for (let i = 0; i < keys.length; i++) {
let newPath = filePath + keys[i]
if (fs.existsSync(newPath)) return newPath
}
throw new Error('module not found')
}
接着new Modul 拿到绝对路径创造一个模块
1.创造一个绝对引用地址,方便后续读取
2.根据路径创造一个模块
最终需要导出module.export 默认是空对象
function Module (id) {
this.id = id
this.exports = {}
}
function req (filename) {
filename = Module._resolveFilename(filename)
const module = new Module(filename)
return module.exports
}
接着调用原型方法module.load 对模块进行加载
module.load()的核心 就是让用户给module.exports 赋值
原型load不需要传任何参数,因为里面有this,指的就是module
接着根据文件后缀 Module._extensions[".js"] 去做策略加载
那我们怎么拿文件的后缀名呢?
根据文件的path.extname(),传入当前this.id,就可以获取当前文件的后缀名,接着就调用对应的策略,再把当前模块传进去,这样的好处就是不管什么样的文件都可以采用不同的策略,后续的逻辑只需要在策略里面更改
Module.prototype.load = function () {
let ext = path.extname(this.id) // 获取文件后缀名
Module._extensions[ext](this)
}
function req (filename) {
filename = Module._resolveFilename(filename)
const module = new Module(filename)
module.load()
return module.exports
}
接着就是读取文件了,js文件第一步需要先读取脚本,再包装一个模板函数(function(exports,module,require,__dirname,__filename){${script}})
,这个目前只是一个字符串,我们需要用vm.runInThisContext()把他变成函数
接着我们需要让函数执行,那就得用fn.call(),函数的call 的作用 1.改变this指向 2.让函数指向
Module._extensions = {
'.js' (module) {
let script = fs.readFileSync(module.id, 'utf8')
let templateFn = `(function(exports,module,require,__dirname,__filename){${script}})`
let fn = vm.runInThisContext(templateFn)
let exports = module.exports
let thisValue = exports
let filename = module.id
let dirname = path.dirname(filename)
fn.call(thisValue, exports, module, req, dirname, filename)
},
'.json' (module) {
let script = fs.readFileSync(module.id, 'utf8')
module.exports = JSON.parse(script)
},
}
现在还有小问题,就是模块没有缓存,会导致多次引入,多次输出
解决方法:创建一个Module._catch,每当创造一个模块,我们就加上一个缓存,根据文件名,因为文件名是独一无二的,我们再req的时候先判断,有缓存的模块直接返回就行了
Module._catch = {}
function req (filename) {
filename = Module._resolveFilename(filename)
let cacheModule = Module._catch[filename]
if (cacheModule) return cacheModule.exports
const module = new Module(filename)
Module._catch[filename] = module
module.load()
return module.exports
}
好了,一个简写的require方法就完成了,我把完整代码放在下面(代码有注释哦~)
const fs = require('fs')
const path = require('path')
const vm = require('vm')
function Module (id) {
this.id = id
this.exports = {}
}
Module._catch = {}
Module._extensions = {
'.js' (module) {
let script = fs.readFileSync(module.id, 'utf8')
let templateFn = `(function(exports,module,require,__dirname,__filename){${script}})`
let fn = vm.runInThisContext(templateFn)
let exports = module.exports
let thisValue = exports // this = module.exports = exports
let filename = module.id
let dirname = path.dirname(filename)
// 函数的call 的作用 1.改变this指向 2.让函数指向
fn.call(thisValue, exports, module, req, dirname, filename) // 调用了a模块
},
'.json' (module) {
let script = fs.readFileSync(module.id, 'utf8')
module.exports = JSON.parse(script)
},
}
Module._resolveFilename = function (id) {
let filePath = path.resolve(__dirname, id)
let isExists = fs.existsSync(filePath)
if (isExists) return filePath
// 尝试添加后缀
let keys = Reflect.ownKeys(Module._extensions) // 以后object新出的方法 都会放到Reflect上
for (let i = 0; i < keys.length; i++) {
let newPath = filePath + keys[i]
if (fs.existsSync(newPath)) return newPath
}
throw new Error('module not found')
}
Module.prototype.load = function () {
let ext = path.extname(this.id) // 获取文件后缀名
Module._extensions[ext](this)
}
function req (filename) {
filename = Module._resolveFilename(filename) // 1.创造一个绝对引用地址,方便后续读取
let cacheModule = Module._catch[filename]
if (cacheModule) return cacheModule.exports // 直接将上次缓存的模块丢给你就ok了
const module = new Module(filename) // 2.根据路径创造一个模块
Module._catch[filename] = module // 最终:缓存模块 根据的是文件名来缓存
module.load() // 就是让用户给module.exports 赋值
return module.exports // 默认是空对象
}