Nodejs 模块 (二)

手写require 继上一篇 深入

  • node中js文件就是一个模块
  • 为什么出现模块的概念 防止命名冲突、可以把同样的功能封装到一起
  • esModule commonjs规范(一个文件是一个模块,module.exports导出给别人使用,require来引用别人的模块)

新建 a.json

1
2
3
{
"name":"加菲"
}

b.js

1
module.exports = 'hello';

手写require模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// node模块会按照后缀名查找.js文件是否存在,再查找.json文件
const path = require('path');
const fs = require('fs');
const vm = require('vm');
function Module(id){
this.id = id;
this.exports = {}; //模块的结果

}
Module.wrapper = [
'(function(exports,module,require,__filename,__dirname){',
'})'
]
Module.extension = {
'.js'(module){ // js文件需要将exports传入给用户,自己赋值
let script = fs.readFileSync(module.id, 'utf8');
// module.exports = 'hello';
let fnStr = Module.wrapper[0] + script + Module.wrapper[1];
let fn = vm.runInThisContext(fnStr); // 让字符串变成js代码
// b.js里面打印console.log(this); 结果:module.exports,所以这里的call中的this是module.exports;
// b.js文件中可能会有其他的require,所以下面是req 其实也就是我手写的这个req
fn.call(module.exports, module, module.exports, req, module.id, path.dirname(module.id));
//call第一个参数是改变this指向
},
'.json'(module){ // 解析后 node默认会赋值
let script = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(script);
}
}
// 给你一个相对路径 解析成绝对路径
Module.resolveFilename = function(filename){
// 把相对路径转化成绝对路径 默认会先判断是否是绝对路径
let absPath = path.resolve(__dirname, filename);
let flag = fs.existsSync(absPath);
let current = absPath; // 默认是当前路径
if( !flag){
let keys = Object.keys(Module.extension);
for(let i = 0; i< keys.length; i++){
current = absPath + keys[i]; //当前找到的文件路径
let flag = fs.existsSync(current);
if(flag){
break;
}else{
current = null;
}
}
}
if( !current){
throw new Error('文件不存在');
}
return current; // 返回的文件路径
}
Module.prototype.load = function(){
// 模块加载就是读取文件的内容
let ext = path.extname(this.id);
Module.extension[ext](this); // 根据不同的后缀 调用不同的处理方法
}
Module._cache = {};
function req(filename){// 自己实现了一个require方法
let current = Module.resolveFilename(filename);
if(Module._cache[current]){ // 相同的文件路径只加载一次
return Module._cache[current].exports;
}
let module = new Module(current); // 产生了一个module
Module._cache[current] = module;
module.load();

return module.exports; // 默认导出module.exports对象
}
// let json = req('./a');
// console.log('json:', json);

let js = req('./b');
req('./b'); // 相同文件路径只缓存一次 line65
console.log('js:', js);
  • node中的模块分3类 文件模块 第三方模块(需要安装) 内置模块 fs path…
  • 模块的查找路径:
    现在查找的是b.js,如果有一个b的文件夹 ,先查找文件,找不到再找文件夹
  • 第三方模块查找:
    比如说:require(‘commander’); 他会找当前文件夹下的node_modules,再找同名的文件(commander),如果有package.json下{main:’xxx’},那就会找入口文件main对应的文件,再找index.js,再找index.json,找不到会向上级的node_modules查找,直到根目录,根目录也找不到就报错;