Nodejs 模块 (一)

知识点

es模块、commonjs模块

  • 它们的特点都是一样的 每个文件都是一个模块
    commonjs只是一个规范
    1)每个文件都是一个模块
    2)如果要使用 就需要require
    3)如果要给别人用 就需要module.exports

怎么实现模块化? 防止命名冲突
命名空间 无法彻底解决命名问题
自执行函数 node让js拥有了在服务端执行的能力,可以读写文件


模块

新建usea.js、 a.js

1
2
3
4
5
6
7
8
9
10
// a.js
module.exports = 'hello';

/*
(function(){
module.exports = 'hello';
return module.exports;
})
// 虽然只写了line1,但是node内部会包装成line5-8自执行函数
*/
1
2
// usea.js
let a = require('./a');

这里require进来a文件相当于如下:

1
2
3
4
let a = (function(){
module.exports = 'hello';
return module.exports;
})();

怎么实现模块化的 传进来一个匿名函数,让函数执行,拿到它导出来的结果 就可以了

node之所以能实现CommonJS规范,靠的是文件读写,将a文件读出来,加上自执行这个函数 一执行就好了。

fs模块

1
2
3
4
const fs = require('fs');
fs.readFileSync
// 判断文件是否存在
fs.accessSync('.gitgore'); // 如果不存在抛出异常

path模块

1
2
3
4
5
6
7
8
9
10
11
const path = require('path'); // 专门处理路径的模块
console.log(path.resolve('./a.js')); // Users/jiafei/Desktop/node-test/a.js
// 它会从当前的跟路径下查找

console.log(__dirname); // /Users/jiafei/Desktop/node-test/8.eventLoop
// 当前路径

console.log(path.resolve(__dirname, './a.js')); // /Users/jiafei/Desktop/node-test/8.eventLoop/a.js
//这样就能获取到当前路径下

console.log(path.resolve(__dirname, './a.js', 'b.js'));// /Users/jiafei/Desktop/node-test/8.eventLoop/a.js/b.js

resolve & join

join 就是单纯的拼路径

1
2
console.log(path.resolve('a', 'b')); //  /Users/jiafei/Desktop/node-test/a/b
console.log(path.join('a', 'b')); // a/b

加 __dirname

1
2
3
4
5
console.log(path.resolve(__dirname, 'a', 'b')); 
// /Users/jiafei/Desktop/node-test/8.eventLoop/a/b

console.log(path.join(__dirname, 'a', 'b'));
// /Users/jiafei/Desktop/node-test/8.eventLoop/a/b

加 /

1
2
3
4
5
6
7
8
// resolve出来的肯定是个绝对路径   join就是以/拼接
console.log(path.resolve(__dirname, 'a', 'b', '/'));
// /Users/jiafei/Desktop/node-test/8.eventLoop/a/b
// /

console.log(path.join(__dirname, 'a', 'b', '/'));
// /Users/jiafei/Desktop/node-test/8.eventLoop/a/b/
// 有/ 只能用join

其他

1
2
3
console.log(path.extname('main.js'));// .js
console.log(path.basename('main.js', '.js')); // main
console.log(path.dirname(__dirname)); // /Users/jiafei/Desktop/ zhufeng 获取父路径

vm模块

它提供了一个沙箱环境(一个安全的环境,不会影响其它)

1
require('vm'); // 虚拟机模块
1
2
3
4
5
6
7
8
9
/*这里 引入a
let a = require('./a');
其实就是把 module.exports = 'hello';这段代码拿过来 ,套一个函数
(function(){
module.exports = 'hello';
return module.exports;
})
其实套完函数 它还是一个字符串 让一个字符串执行
*/

让字符串执行 new Function \ eval \ vm

1
2
let a = 1;
eval('console.log(a)'); // 1

这时a.js中的代码如下:

1
2
3
// 供沙箱示例
console.log(b);
module.exports = 'hello';

usea.js

1
2
3
4
5
6
7
8
9
10
// let b = XX;
// q这里的值就是引入的a.js中沙箱实例的代码
let q = (function(){
console.log(b);
module.exports = 'hello';
return module.exports;
})
// 如果这时usea模块line1 有 let b = XX;那就是line3-7这个模块拿到了usea模块的b的值,那乱了,希望的是a模块不能拿到外部的值
let vm = require('vm');
vm.runInThisContext('console.log(b)'); // ReferenceError: b is not defined

文件读写

手写require模块

a.js

1
2
3
4
// (function(exports,module,require){
module.exports = 'hello';
//})
// 这里其实内部是这样的

usea.js

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
const path = require('path');
const fs = require('fs');
const vm = require('vm');
const wrapper = [ // module 和exports是什么关系 export是module的属性
'(function(exports,module,require){',
'})'
]

function Module(absPath){ // 创建模块
this.id = absPath;
this.exports = {};
}
Module.prototype.load = function () {
// console.log(this.id); // 文件路径
let script = fs.readFileSync(this.id, 'utf8');
// module.exports = 'hello';
// 现在取到内容了在外面加自执行函数
let fnStr = wrapper[0] + script + wrapper[1];
// 让字符串执行
let fn = vm.runInThisContext(fnStr);
fn(this.exports, this, req); // 让拼出的函数执行
console.log(this.exports); // 上面这行fn函数执行了,那就是a文件里把值传给了exports 打印
// hello


}
function req(file) { // ./a.js
// 1) 把当前这个文件读取出来 把相对路径转化成绝对路径
let absPath = path.resolve(__dirname, file);
// /Users/jiafei/Desktop/node-test/8.eventLoop/a2
// 加载一个模块 模块就是要有一个exports属性
// 2) 创建一个模块
let module = new Module(absPath); // 创建了一个模块
// 3)加载模块
module.load();
return module.exports;
}

let a = req('./a2.js');
console.log("a:", a); // a: hello

源码 require() 核心逻辑

  • Module._resolveFilename 解析文件名字 获取文件绝对路径
  • module.load(filename); 加载模块
  • fs.readFileSync(filename, ‘utf8’); 同步的读取文件内容
  • 加函数
  • 让函数执行 module.exports 传入给用户 用户会给module.exports赋值
  • 把module.exports返回