CommonJS 模块的特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
module 对象
module.id
模块的识别符,通常是带有绝对路径的模块文件名。module.filename
模块的文件名,带有绝对路径。module.loaded
返回一个布尔值,表示模块是否已经完成加载。module.parent
返回一个对象,表示调用该模块的模块。module.children
返回一个数组,表示该模块要用到的其他模块。module.exports
表示模块对外输出的值。
1 |
|
module.exports
- module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。
exports 变量
- 为了方便,Node 为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令。
var exports = module.exports;
- 注意,不能直接将 exports 变量指向一个值,因为这样等于切断了 exports 与 module.exports 的联系。
1 |
|
通过例子我们可以看到,如果改变了exports
指向的值,那么,就不会生效
require
- require 命令的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象。如果没有发现指定模块,会报错。
- require 命令用于加载文件,后缀名默认为.js
模块的缓存
- 第一次加载某个模块时,Node 会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的 module.exports 属性。
1 |
|
我们可以看到,第三次加载的时候,message
依然存在,证明 require 只是输出了缓存。
- 所有缓存的模块保存在 require.cache 之中,如果想删除模块的缓存,可以像下面这样写。
1 |
|
我们将删除语句加入上面的代码,可以看到message
的缓存被去除了
1 |
|
es module
导出和导入
我们使用export
来导出模块,通过import
来导入模块。
1 |
|
- 默认导出
export default
我们在export.js
中加一条
1 |
|
1 |
|
重命名导出与导入
在你的 import
和 export
语句的大括号中,可以使用 as
关键字跟一个新的名字,来改变你在顶级模块中将要使用的功能的标识名字。因此,例如,以下两者都会做同样的工作,尽管方式略有不同:
1 |
|
1 |
|
创建模块对象
当导出项过多时,import { a, b, c, d, e, f } from './xxx'
,看着会有一点点混乱和冗长。我们可以用一下语法形式更好的解决:
1 |
|
CommonJs 和 esModule 的区别
commonJs
是被加载的时候运行,esModule
是编译的时候运行commonJs
输出的是值的浅拷贝,esModule
输出值的引用commonJs
具有缓存。在第一次被加载时,会完整运行整个文件并输出一个对象,拷贝(浅拷贝)在内存中。下次加载文件时,直接从内存中取值(前文已证明)
CommonJs
1 |
|
从count
我们可以得出,CommonJs
是值的拷贝,从obj
我们可以得出CommonJs
是浅拷贝。
esModule
1 |
|
循环引用
CommonJs
我们先来看CommonJs
中的循环引用
1 |
|
为什么会输出这样的结果呢,因为CommonJs
是加载时运行,或者说是同步执行
的,当a.js
中引入b.js
的时候,a
中name
的第二次赋值还没有执行,所以b.js
中拿到的a.name
是第一次赋值的结果,而a.js
中引入了b.js
,因为b.js
已经执行完毕,所以拿到的name
是第二次赋值后的结果。
esModule
1 |
|
在esModule
中,因为模块都是值的引用
,所以esModule
根本不会去关心是否是循环引用,只要保证代码不会陷入死循环就可以。
关于循环引用可以参考阮大大的JavaScript 模块的循环加载
<完> 完>