1.概述
node默认使用的是commonjs模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
如果要共享只能挂在全局变量global上(不推荐这种做法ヽ(`Д´)ノ):
global.plat = “PC”;
或者通过模块引用的方式(推荐(๑•̀ㅂ•́)و✧):。
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
2. 加载
CommonJS使用require
来引入模块,使用exports
或module.exports
来导出模块(module);
const { read, write } = require('./file');
CommonJS只能在运行时才能确定模块的引用及依赖关系。上述代码其实是先加载file
这个整个对象,之后再取上面的属性。
const file = require('./file');
const read = file.read
const write = file.write
ps: 但是es6是通过export命令直接导出,在编译时就可以判断要加载的方法即我们所说的静态编译。为后续的类型校验和宏提供了可能
3.module
Node内部有个Module的构造函数,每个文件中的module都是其的一个实例,module有以下几个属性:
- id 模块的识别符,通常是带有绝对路径的模块文件名。
- path 模块的路径,模块文件对应的路径。
- exports 表示模块对外输出的值。
- parent 返回一个对象,表示调用该模块的模块,也是Module的一个实例。
- filename 模块的文件名,带有绝对路径。
- loaded 返回一个布尔值,表示模块是否已经完成加载。
- children 返回一个数组,表示该模块要用到的其他模块。
- paths 返回一个数组,表示模块的搜索路径。
随便找个文件,打印一下console.log(module)
:
Module {
id: '/Users/pub/f-node-encryption/dist/index.js',
path: '/Users/pub/f-node-encryption/dist',
exports: {},
parent: Module {
id: '.',
path: '/Users/pub/f-node-encryption/test',
exports: {},
parent: null,
filename: '/Users/pub/f-node-encryption/test/index.js',
loaded: false,
children: [ [Circular] ],
paths: [
'/Users/pubf-node-encryption/test/node_modules',
'/Users/pub/f-node-encryption/node_modules',
'/Users/pub/node_modules',
'/Users/node_modules',
'/node_modules'
]
},
filename: '/Users/pub/f-node-encryption/dist/index.js',
loaded: false,
children: [
Module {
id: '/Users/pub/f-node-encryption/dist/src/lib/util.js',
path: '/Users/pub/f-node-encryption/dist/src/lib',
exports: [Object],
parent: [Circular],
filename: '/Users/pub/f-node-encryption/dist/src/lib/util.js',
loaded: true,
children: [Array],
paths: [Array]
}
],
paths: [
'/Users/pub/f-node-encryption/dist/node_modules',
'/Users/pub/f-node-encryption/node_modules',
'/Users/pub/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
node引用模块很简单,在文件内部使用exports
或module.exports
来导出。举个🌰
// a.js 定义一个TestClass类
class TestClass {
constructor(){}
.....
}
module.exports = TestClass
// b.js 引用并实例化
const TestClass = require("./a")
cosnt curTest = new TestClass()
....
console.log(exports===module.exports) // 输出true
这里使用module.exports
来进行导出,当然也可以使用 exports
, 但是!!!我们不能直接给 exports
赋值,像这样:
exports = TestClass
这是不可以的!!!因为使用的exports
其实是module.exports
的引用,如果直接赋值会使引用断开,此时在b.js
内打印TestClass
会输出module.exports
的值,此时就是一个空对象。
再次重申,模块导出导出的是module.exports
的值!!!
当然在保留引用的前提下可以进行赋值:
// a,js
exports.test = TestClass
// b.js
const TestClass = require("./a")
cosnt curTest = new (TestClass.test)()
4. CommonJS模块的特点。
主要有以下三点:
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存(delete require.cache[moduleName])。
- 模块加载的顺序,按照其在代码中出现的顺序。
关于第二条还想仔细说一下
举个缓存的🌰:
// a.js
var tmp = {name::“Jack”};
module.exports = tmp;
// b.js
const OnceTest = require("./a")
// 输出OnceTest为 {name:“Jack”}
console.log("OnceTest:", OnceTest)
// 给 OnceTest添加新的属性
OnceTest.age = 21;
// 再次引入相同模块
const TwiceTest = require("./a")
// 此时打印TwiceTest,发现里面也有age属性
console.log("TwiceTest:", TwiceTest) // 输出{name:“Jack”, age:21}
如果我想清除缓存那么需要修改下b.js:
const OnceTest = require("./a")
console.log("OnceTest:", OnceTest)
OnceTest.age = 21;
// 先要清除引入模块的缓存
delete require.cache['/Users/pub/f-node-encryption/a.js']
// 再次引入相同模块
const TwiceTest = require("./a")
// 此时打印TwiceTest,发现已经是从a.js新引入的模块了
console.log("TwiceTest:", TwiceTest) // 输出{name:“Jack”}
详细cache里的内容可以打印require.cache
查看。
这里贴一个清除缓存时存在的问题 传送门
4.顺便记一下ES中的模块使用
在ES中的模块使用相对灵活,使用export
export detault
来导出,使用import
来引用。
我们先看一下export
和 export default
这两个导出方式有什么不同
- 在一个文件中,export可以有多个但export default只能有一个。
- 通过export方式导出,在导入时要加{ },export default则不需要。
- export能直接导出变量表达式,export default不行。
// 导出变量
export const a = 123;
// 导出方法
export const getName = function(){}
// 导出方法
function getName(){}
export {getName}
// excport default 导出变量
const a = 123;
export default a
// 导入
// 使用 export 方法导出
import { dogSay, catSay } from './test';
// 使用export default导出
import a from './test';
// as 集合成对象导出
import * as testModule from './test'; //as 集合成对象导出
其他细节不再次赘述啦,感兴趣的小伙伴自行学习;
还有一点需要注意一下,因为import
是静态解析的,所以在引入前使用也是不会报错的:
dogSay()
import { dogSay, catSay } from './test';
但是 不可以在这样“动态”使用import
const moduleName = "dog"
import { `${moduleName}Say` } from './test';
// 或者这样
if(moduleName === "dog"){
import { dogSay`} from './test';
}
写的有点凌乱回头在整理一波= =
网友评论