理解require原理和实现一个require

require 内部流程

咱们先大体了解一下 require()引入一个 `.js` 或者 `.json` 文件时候,内部大体是怎么处理的。 node

let name = require('./a')
    console.log(name)
复制代码

上述代码引入 a.js 文件时候,内部大体发生的流程以下:git

一、将 ./a 转化为绝对路径,而且补充后缀名(c:\Users\chenying\Desktop\code\a.js)github

二、根据绝对路径判断缓存中是否存在缓存的文件,若是存在则取缓存,不存在则继续json

三、建立 Module 实例 module,将绝对路径传入缓存

四、取得绝对路径的后缀名,根据后缀名(.js)调用对应的处理函数app

五、读 .js.json 文件思路大同小异,经过fs.readFileSync()读取文件内容函数

六、对读到的 .js 文件的内容外层包裹一个函数,而且将字符串转成函数执行ui

七、对读到的 .json 文件的内容,转为对象,而且赋值给module.exportsthis

::: tip 咱们再来看下源码中对如下几个疑惑点是怎么进行处理的 :::spa

一、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的实现

咱们如今根据步骤结合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`
} 
复制代码

目前已经根据源码思路来实现一款mini版require(),可是还有相似于实现缓存、自动补全后缀名等功能就不贴代码了,能够看下方的源码,欢迎star

源码

点击查看源码

最后

文章可能有不足的地方,请你们见谅,若是有什么疑问能够下方留言讨论。

相关文章
相关标签/搜索