# 你不知道的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 上挂载很多,但是我们真正需要接触到的分别是 idexports,他们分别是绝对路径,和默认导出的空对象

# 取得绝对路径的后缀名,根据后缀名(.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

# 源码

点击查看源码