相信大家平时写代码都使用过require,那么今天我们简单的写写这个原理。
首先先了解下前端有几种模块分别是干什么的:前端模块规范有三种:CommonJs,AMD和CMD。
2.AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
3.CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
4.AMD:提前执行(异步加载:依赖先执行)+延迟执行
5.CMD:延迟执行(运行到需加载,根据顺序执行)
先看看commonjs 规范
let str = 'hello world';
module.exports = str;
开始使用第一个包,这个里面要注意的一点就是如果是我们自己写的文件模块要写路径
let str = require('./str.js');
console.log(str);
好了,自己的模块现在就写出来了,那么我们现在看看这个里面都发生了些什么。
如果是第一次加载,首先是Module._load模块加载,Module._resolveFilename 模块解析文件名解析出一个绝对路径出来使用tryModuleLoad()尝试加载模块,先看有没有后缀名,没有后缀名则加上后缀名去目录底下寻找这个文件,找到了就开始读取文件内容执行文件代码(本质上就是创建一个匿名函数创建一个沙箱,沙箱里面执行代码并返回结果当模块被加载的时候),并创建这个模块没找到或者代码异常不合法则会抛出错误,并把这个模块加入到Module._cache模块进行缓存,多次require只会走一次这个这个读取流程,如果不是第一次加载那么会读Module._cache模块的缓存。
不过代码执行的时候会有些问题,比如常见的eval和new Function,但是这个会有问题,因为eval是不干净的执行,eval是依赖于上下文环境可能会污染变量。
let a = 'aaa';
eval('console.log(a)')
new function会把模块变成匿名函数,缺点也是不干净执行,依赖于上下文关系,容易变成互相引用。所以node里面执行代码就使用了vm这个沙箱,这个沙箱不依赖于外部环境,他的原理就是创建一个闭包开始执行函数并返回结果。
我们按照这个思路来写下:
let fs = require('fs');
let vm = require('vm');
let path = require('path');
function Module(id) {
this.id = id;
this.exports = {}
}
Module.wrapper = [
"(function (exports, require, module, __filename, __dirname) {",
"})"
]
Module.wrap = function (script) {
return Module.wrapper[0] + script+ Module.wrapper[1];
}
Module._extensions = {
'.js':function (module) {
let content = fs.readFileSync(module.id, 'utf8');
let funcStr = Module.wrap(content);
let fn = vm.runInThisContext(funcStr);
fn.call(module.exports,module.exports,req,module); // exports = {}
},
'.json':function (module) {
module.exports = JSON.parse(fs.readFileSync(module.id, 'utf8'));
}
}
// 解析文件名
Module._resolveFilename = function (p) {
if((/\.js$|\.json$/).test(p)){
// 以js或者json结尾的
return path.resolve(__dirname, p);
}else{
// 没有后后缀 自动拼后缀
let exts = Object.keys(Module._extensions);
let realPath;
for (let i = 0; i < exts.length; i++) {
let temp = path.resolve(__dirname, p + exts[i]);
try {
fs.accessSync(temp); // 存在的
realPath = temp
break;
} catch (e) {
}
}
if(!realPath){
throw new Error('module not exists');
}
return realPath
}
}
Module._cache = {};
function tryModuleLoad(module){
let ext = path.extname(module.id);//扩展名
// 如果扩展名是js 调用js处理器 如果是json 调用json处理器
Module._extensions[ext](module); // exports 上就有了数组
}
Module._load = function (p) { // 相对路径,可能这个文件没有后缀,尝试加后缀
let filename = Module._resolveFilename(p); // 获取到绝对路径
let cache = Module._cache[filename];
if(cache){ // 第一次没有缓存 不会进来
return cache.exports;
}
let module = new Module(filename); // 没有模块就创建模块
Module._cache[filename] = module;// 每个模块都有exports对象 {}
//尝试加载模块
tryModuleLoad(module);
return module.exports
}
function req(p) {
return Module._load(p); // 加载模块
}
let str1 = req('./str.js');
str2 = req('./str.js'); // 缓存靠的就是绝对路径来缓存的
ok,这个就是require原理,学了包那么就自己写一个包发布到npm上开始试(作)验(死)之旅吧。
最后是整体node加载包的流程图,有兴趣的同学可以写写试试看(*^▽^*)。
node模块加载策略。

文件模块查找规则

首先你得有个npm账号,这个去npm官网上注册一个就可以了,选个文件夹npm init然后开始写代码,写完代码之后npm login 输入你的npm信息之后npm publish这样就完事了,如果你想删除自己的包npm --force unpublish 包名。
最后推荐一个包nrm,这个包可以切换下载包的源,里面不用配置直接使用,安装方法npm i nrm -g,查看包源nrm ls ,切换包源nrm use cnpm
浙公网安备 33010602011771号