commonJS -require原理及总结

124 阅读2分钟
const path = require('path')
const fs = require('fs')
//vm 类似new Function() 制造一个沙箱环境
const vm = require('vm')
function Module(id) {
    this.id = id
    this.exports = {}
}
//拼接一个函数字符串  放到vm中解析成一个函数
Module.warp = function(script){
    let arr = [
        `(function (exports,require,module,__filename,__dirname){`,
        script,
        `})`
    ]
    return arr.join('')
}
Module._extensions = {
    '.js': function (module) {
        // 读取当前文件
        let content =  fs.readFileSync(module.id,'utf8')
        // 将读取到的文件 拼接成字符串函数
        let fnStr = Module.warp(content)
        //将字符串函数转化为函数
        let fn = vm.runInThisContext(fnStr)
        let exports = module.exports
        let require = myRequire
        //当前文件的绝对路径
        let __filename = module.id
        //当前文件夹绝对路径
        let __dirname = path.dirname(module.id)
        //执行转化的函数传入值
        fn.call(exports,exports,require,module,__filename,__dirname)
     },
    '.json': function (module) {
       let content =  fs.readFileSync(module.id,'utf8')
       Module.exports =  JSON.parse(content)
     },
    '.node': function (module) { }
}

//解析传入文件,转成绝对路径
Module._resolveFilename = function (filePatha) {
    let filePath = path.resolve(__dirname, filePatha)
    //require时传入的文件是否有文件后缀  ---.js/.josn/.node
    let exists = fs.existsSync(filePath)
    //如果有直接返回路径
    if (exists) return filePath
    //没有写后缀 就拿 Module._extensions中的 对象key值来拼接路径
    let keys = Object.keys(Module._extensions)
    for (let i = 0; i < keys.length; i++) {
        let currentPath = filePatha + keys[i]
        //如果拼接的文件存在 返回路径
        if(fs.existsSync(currentPath)) return currentPath
    }
}
//
Module.prototype.load = function(filename){
    //拿到文件的后缀名
   let extname = path.extname(filename)
   //用后缀名去策略里面调用对应的方法
   Module._extensions[extname](this)
}
Module._load = function (filePatha) {
    //拿到require时传入的文件的 绝对路径
    let fileName = Module._resolveFilename(filePatha)
    //拿到完整的路径就创建一个 module对象
    let module = new Module(fileName)
    // 调用module.lode方法传入 完整的路径
    module.load(fileName)

    return module.exports
}

function myRequire(filePath) {
    //返回的是Module的私有方法调用
    return Module._load(filePath)
}
// 总结
//1.require语法时同步语法,内部用的时fs.readfileSync
//2.最终require的语法最终返回的是module.exports
//3.模块的exports 和 module.exports 引用的是同一个变量
//4.模块是动态加载每次require都会获取最新的导出结果,可以将require卸载条件中
//5.更改exports的引用 不会导致module.exports变化
//6.require一般不能循环引用,如果循环引用只能加载部分数据
let r = myRequire('./a.js')
console.log(r)