CommonJS模块规范
- 一个js文件就是一个模块
- CommonJS 就是一套约定标准,不是技术; 用于约定我们的代码应该是怎样的一种结构。
- Node 采用的模块化结构是按照 CommonJS 规范。
- CommonJS的特点:
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
- 模块的分类
- 自定义模块:即我们自己写的功能模块文件。
- 核心模块:即Node自带的功能模块,如:HTTP模块,fs模块等等。
- 第三方模块:社区或第三方开发好的功能模块,可以直接拿回来用。
- 模块的导入
- 通过
require("fs")
来加载模块
- 如果是第三方模块,需要先使用
npm
进行下载
- 如果是自定义模块,需要加上相对路径./或者../,可以省略.js后缀,如果文件名是index.js那么index.js也可以省略
- 模块可以被多次加载,但是只会在第一次加载
module.exports与exports
- 载入一个模块就是构建一个 Module 实例,一个新的 JS 文件就是一个模块
-
module.exports
是用于为模块导出成员的接口。
- 在模块的内部,
module
变量代表的就是当前模块,它的exports
属性就是对外的接口,加载某个模块,加载的就是module.exports
属性,这个属性指向一个空的对象。
-
exports
是指向module.exports
的引用,相当于在模块开始执行的时候进行var exports = module.exports
操作。从下面的打印中可以看出,它们最初都是一个空对象{}
,而这两个对象实际上指向同一块内存空间,即在不改变它们指向的内存地址的前提下,它们是等价的。
console.log(exports) //{}
console.log(module) //Module {……exports: {},parent: null……}
console.log(exports === module.exports) //true
-
require
引入的对象本质上是module.exports
,这也意味着module.exports
与exports
指向的不是同一块内存时,exports
的内容就会失效。
//try.js
exports = {name: "Join"}
module.exports = {name: "Bob"}
//main.js
let name = require('./try.js')
console.log(name) //{ name: 'Bob' }
require加载文件规则
- require('../file.js'); // 上级目录下找 file.js 文件
require('./file.js'); // 同级目录找 file.js 文件
require('file.js'); // 同级目录找 file.js 文件
- 加载顺序:
- 按js文件来执行(先找对应路径当中的module.js文件来加载)
- 按json文件来解析(若上面的js文件找不到时,则找对应路径当中的module.json文件来加载)
- 按照预编译好的c++模块来执行(寻找对应路径当中的module.node文件来加载)
- 若参数字符串为一个目录(文件夹)的路径,则自动先查找该文件夹下的package.json文件,然后再再加载该文件当中main字段所指定的入口文件。(若package.json文件当中没有main字段,或者根本没有package.json文件,则再默认查找该文件夹下的index.js文件作为模块来载入。)
CommonJS引入与ES6的区别
- CommonJS是直接做一个值的拷贝操作,也就是一旦输出一个值,模块内部的变化是影响不到这个值的
//try.js
let counter = 1
let addCounter = () => {
counter++
}
module.exports = {
counter,
addCounter
}
//main.js
let func = require('./try.js')
console.log(func.counter) //1
func.addCounter()
console.log(func.counter) //1
- ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
//try.js
export let counter = 1
export let addCounter = () => {
counter++
}
//main.js
import {counter,addCounter} from './try.js'
console.log(counter) //1
addCounter()
console.log(counter) //2
- CommonJS模块的循环引用
- 执行
node main.js
->第一行,require(a.js)
- 进入
require(a)
方法:判断缓存->无->初始化一个module
->将module
加入缓存->执行a.js内容
- 第一行导出
a=1
->第二行引入b.js
- 执行b.js的内容,第一行导出
b=11
,第二行require(a.js)
- 此时a.js是第二次调用require,判断缓存->有->继续执行b.js->第三行打印
1
->第四行修改b=22
- b文件执行完毕回到a.js中->第三行打印
b=22
->导出a=2
- a文件执行完毕,回到main.js中->获取a,第二行输出
a=2
->执行完毕
// a.js
module.exports.a = 1;
var b = require('./b');
console.log(b);
module.exports.a = 2;
// b.js
module.exports.b = 11;
var a = require('./a');
console.log(a);
module.exports.b = 22;
//main.js
var a = require('./a');
console.log(a);
Node加载
- Node 要求使用 ES6 模块需要采用
.mjs
后缀文件名。也就是说,Node 遇到.mjs
文件,就认为它是ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"
。
- 如果不希望将后缀名改成
.mjs
,可以在项目的package.json
文件中,指定type
字段为module
。一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 Module
。如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 模块脚本的后缀名都改成.cjs
。如果没有type
字段,或者type
字段为commonjs
,则.js脚本会被解释成 CommonJS 模块。
{
"type": "module" // 开启 ES6 Module 模式
}