文章目录
- 1. 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
- 2. 每个模块内部,module变量代表当前模块。
- 3. 两种导出方式 exports 和 module.exports
- 4. 一种引入方式 require
- 5. 模块化的实现原理
- 6. CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- 7. nodejs的全局对象是global
- 8. CommonJS 模块的require()是同步加载模块
1. 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
a.js
var a = 1;
b.js
console.log(a) // 会报错 a is not define
2. 每个模块内部,module变量代表当前模块。
console.log(module)可以查看该模块的module对象
Module {
id: '.', // 模块唯一识别符,
path: 'D:\\Codes\\Test', // 模块所在的文件路径
exports: {}, // 模块的导出对象,这也是我们用module.exports导出的原因
parent: null, // 如果该模块被其它模块引用,这里就是引用它的模块
filename: 'D:\\Codes\\Test\\node1.js', // 模块的名称,通常是模块对应的完整路径
loaded: false, // 模块是否已经加载完成
children: [], // 模块里是否引入了其它模块
paths: [
'D:\\Codes\\Test\\node_modules',
'D:\\Codes\\node_modules',
'D:\\node_modules'
]
}
在前端开发中,一开始不存在模块化,比如一个html文件
<!DOCTYPE html>
<html lang="en">
<head>
<script src="./a.js"></script>
<script src="./b.js"></script>
</head>
</html>
a和b看似是两个js,但都被放在html,最终被浏览器整合了,形成统一代码执行环境,又称上下文。
在html中如何做到a与b独立呢?
<!DOCTYPE html>
<html lang="en">
<head>
<script src="./a.js" type="module"></script>
<script src="./b.js" type="module"></script>
</head>
</html>
那如何做到模块与模块之间通信呢,我们总不能把所有的逻辑一口气写到一个js文件里吧,自然得把需要的东西引入,把别的js文件需要的东西导出。
3. 两种导出方式 exports 和 module.exports
exports是module.exports的引用,这也是它们的区别。
module.exports === 特殊对象,这个对象可以被导出,在其它模块被引用 // true
// module.exports赋值给了exports
exports = module.exports
如果我们对exports赋值会发生什么
// exports 被重新赋值了一个普通对象,这个普通对象不可以被导出,其它模块自然也用不了name属性了
exports = { name: 'dx' }
// 正确的使用exports的方式
exports.name = 'dx';
但用module.exports这样写完全没问题
module.exports = {
name: "dx",
age: 18,
};
4. 一种引入方式 require
4.1引入的内容是有缓存的,多次引入同一文件,只会执行一次
require引入就会执行,并且只会在第一次引入的时候执行,之后缓存在require.catch中,后面再引入,直接从require.catch取出对应的值,不再执行被引入模块本身
b.js代码如下
console.log('b')
require('./b.js')
require('./b.js')
require('./b.js')
// b
当然我们也可以手动消除这种缓存机制,所有缓存的模块保存在require.cache之中
// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
4.2 require引入的三种写法
只有js文件引入的时候,后缀可以省略
const b = require('./b')
路径./开头和/开头也是有区别的
/b表示绝对路径下的b.js
./b 表示相对路径下的b.js
如果没有路径,只有一个文件名
const dx = require('dx')
nodejs会认为有两种情况,第一种,第三方下载的node模块,第二种,node内置的模块。
如果dx是node内置的模块,那肯定没问题,能成功找到,不会报错。
- 如果dx是第三方下载的node模块,那么会先查看环境变量NODE_PATH的路径,如果没有
- 同级node_module文件夹下的dx模块,相当于./node_module/dx
- 如果同级文件夹没有,会找父级目录下的node_module文件夹的dx模块,相当于…/node_module/dx
- 父级也没有,会找父级旳父级 …/…/node_module/dx
- 会一直找到根目录下 C:\node_module\dx
- 最终根目录也没有的话,会报错,说找不到dx模块,并提示你安装这个叫dx的模块 npm i dx --save
4.3 require之后,内部的执行流程
- 检查 Module._cache,是否缓存之中有指定模块
- 如果缓存之中没有,就创建一个新的Module实例
- 将它保存到缓存
- 使用 module.load() 加载指定的模块文件,
读取文件内容之后,使用 module.compile() 执行文件代码 - 如果加载/解析过程报错,就从缓存删除该模块
- 如果加载/解析没问题,返回该模块的 module.exports
5. 模块化的实现原理
a.js
const person = {
name: 'dx',
age: 18
}
console.log(person)
module.exports = person
实际上会变成这样
// 准备module对象:
var module = {
id: 'a.js',
exports: {},
...
};
var exports = module.exports;
var load = function (module) {
const person = {
name: 'dx',
age: 18
}
console.log(person)
module.exports = person
return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);
6. CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
7. nodejs的全局对象是global
平时的JavaScript是三个部分 dom(Document Object Model 文档对象模型)bom(Browser Object Model 浏览器对象模型) 以及 ECMAScript组成。
而nodejs没有dom,也没有bom,所以nodejs的全局对象不是window,也没有history之类的东西。
8. CommonJS 模块的require()是同步加载模块
这句话的意思就是要求,所有的模块导出的数据都必须是同步的
错误示范
setTimeout(() => {
module.exports = { welcome: 'Hello World' }
}, 0)