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 模块的循环加载
<完> 完>