Node模块化----CommonJS
1.什么是CommonJS
CommonJS是一个模块化规范
最初是在浏览器之外的地方使用
- Node是CommonJS在服务端的具体实现
- Broserify是CommonJS在浏览器的实现
- webpack页具备对CommonJS支持与转换
2.CommonJS的具体内容
- 在Node中每一个js文件都是一个单独的模块,即实例module
- CommonJS规范的核心变量:exports、module.exports、require;
3.CommonJS基本语法
obj变量和export变量指向同一块内存。
导出
exports.name = '胡勇'
exports.age = 21
导入方式1
const obj=require("./main")
console.log(obj.name);
console.log(obj.age);
导入方式2
const {name, age} = require('./main')
console.log(name)
console.log(age)
4. module.exports和exports有什么关系或者区别呢
CommonJS并没有module.exports概念,但是Node是Module类实现模块的导出,每一个模块都是一个Module实例,即module。
关系:node会在内部进行初始化,exports=module.exports
区别:导出的时候,实际导出的是module.exports引用的对象。如果exports改变引用的对象,那么exports就没有意义了。
重要:module.exports=exports是在最顶层进行赋值。
下面导出对象为:{},而不是3,即在exports=3之前,module.exports=exports。
exports=3
5.为什么存在exports?
因为CommonJS模块化规范,要求有一个exports作为导出。
6.模块的加载的查找规则
- 核心模块
- 直接返回
- ./…/ 和**/**开头
- 如果有后缀名,去对应目录下,按照后缀名格式查找文件
- 如果没有后缀名 文件名:x
- 第一步:直接查找x,找不到按照x.js,x.json,x.node查找
- 第二步:若有对应的x文件夹,在x文件夹下面,查找index.js,index.json,index.node
- 第三步:如果前面第一步和第二部都没有找到,报错。
- 直接是一个x路径,并且不是核心模块
- 去下面路径所在文件夹,查找x.js,x.json,x.node
7.模块的加载过程
- 模块被引入的过程时候,模块里代码会被执行一遍。
- 模块被引入一次后,会被缓存到其父模块中。再次引入可以直接父模块获取module.exports引用对象的缓存。
- 模块加载如果设计到多个模块的加载,按照深度优先搜索(DFS Depth first search)顺序加载。
下图模块加载的顺序为:aaa,ccc,ddd,eee,bbb
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXsa3JGf-1615690972894)(https://i.loli.net/2021/03/12/xobraBS5eHMcdYF.png)]
8.模块的循环加载过程
CommonJS模块的循环加载,阮一峰的网络日志,写的比较好,这里我直接引用,下面给出连接。
CommonJS模块的重要特性是加载时执行,即脚本代码在require
的时候,就会全部执行。CommonJS的做法是,一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
让我们来看,官方文档里面的例子。脚本文件a.js
代码如下。
exports.done = false; var b = require('./b.js'); console.log('在 a.js 之中,b.done = %j', b.done); exports.done = true; console.log('a.js 执行完毕');
上面代码之中,a.js
脚本先输出一个done
变量,然后加载另一个脚本文件b.js
。注意,此时a.js
代码就停在这里,等待b.js
执行完毕,再往下执行。
再看b.js
的代码。
exports.done = false; var a = require('./a.js'); console.log('在 b.js 之中,a.done = %j', a.done); exports.done = true; console.log('b.js 执行完毕');
上面代码之中,b.js
执行到第二行,就会去加载a.js
,这时,就发生了"循环加载"。系统会去a.js
模块对应对象的exports
属性取值,可是因为a.js
还没有执行完,从exports
属性只能取回已经执行的部分,而不是最后的值。
a.js
已经执行的部分,只有一行。
exports.done = false;
因此,对于b.js
来说,它从a.js
只输入一个变量done
,值为false
。
然后,b.js
接着往下执行,等到全部执行完毕,再把执行权交还给a.js
。于是,a.js
接着往下执行,直到执行完毕。我们写一个脚本main.js
,验证这个过程。
var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
执行main.js
,运行结果如下。
$ node main.js 在 b.js 之中,a.done = false b.js 执行完毕 在 a.js 之中,b.done = true a.js 执行完毕 在 main.js 之中, a.done=true, b.done=true
上面的代码证明了两件事。一是,在b.js
之中,a.js
没有执行完毕,只执行了第一行。二是,main.js
执行到第二行时,不会再次执行b.js
,而是输出缓存的b.js
的执行结果,即它的第四行。
exports.done = true;
9.CommonJS缺点
CommonJS加载模块是同步的,在服务端是本地加载模块,速度比较快。
但是在浏览器,需要将js文件先下载,并且执行完毕,在此之前,后续代码都将不执行,即使是简单的dom操作,因此浏览器可能长时间加载不出来页面,用户体验非常差,不适合于浏览器端。