node中的模块化机制

365 阅读2分钟

     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属性