node的require方法实现原理

2020-02-13  本文已影响0人  简公孙策
const path = require('path');
const fs = require('fs');
const vm = require('vm');

class ZLModule {
    constructor(id) {
        this.id = id;//保存当前模块的绝对路径
        this.exports = {};
    }
}
ZLModule._cache = {};//存储已经加载过的module对象
ZLModule._extensions = {
    '.json': function(module) {
        let json = fs.readFileSync(module.id);
        module.exports = JSON.parse(json);
    },
    '.js': function (module) {
        //将js文件包裹起来,包裹成一个方法
        let script = fs.readFileSync(module.id);
        script = ZLModule.wrapper[0] + '\n' + script + ZLModule.wrapper[1];
        //将字符串转化成方法
        fn = vm.runInThisContext(script);
        fn.call(module.exports,module.exports,require,module);
    }
}
ZLModule.wrapper = ['(function(exports, require, module, __filename, __dirname) {','\n})'];

function ZLRequire(filePath) {
    //1、转化成绝对路径
    let absPath = path.join(__dirname, filePath);
    //2、根据路径从缓存中获取模块,如果没有则新建模块对象(新对象需要加载执行);并最终返回模块对象的exports对象。
    let cacheModule = ZLModule._cache[absPath];
    if(cacheModule) {
        return cacheModule.exports;
    }else {
        let module = new ZLModule(absPath);
        ZLModule._cache[absPath] = module;
        tryModuleLoad(module);//处理模块,主要是处理module.exports对象
        return module.exports; 
    }
}

function tryModuleLoad(module) {
    // 取出模块后缀,根据不同后缀名调用不同的加载方法
    let extName = path.extname(module.id);
    let fn = ZLModule._extensions[extName];
    // ZLModule._extensions[extName](module);
    fn(module);

}

const aModule = ZLRequire('a.js');
一个类Module: 用来定义引入文件(模块的实例,含有两个实例属性,id-保存文件绝对路径和exports-保存文件模块导出对象)

Molude类上保存了两个公共属性:_cache对象(用来存储已经加载过的js模块,通过路径识别);_extensions对象(用来保存不同类型文件的处理方法,根据文件路径后缀名区别)

两个方法: require和tryModuleLoad

require: 用来引入文件(文件路径),直接返回已经加载过的module对象的exports对象;或对于新引入文件创建module实例(并调用tryModuleLoad方法处理文件,得到最终的module.exports对象并返回)
tryModuleLoad: 供require调用,用来处理读取进来的不同类型的文件(根据文件路径名的后缀名进行区分)

注意

上一篇下一篇

猜你喜欢

热点阅读