Node.js入门 03:模块化规范 CommonJS 与 ES Module
Posted Naisu Xu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js入门 03:模块化规范 CommonJS 与 ES Module相关的知识,希望对你有一定的参考价值。
目的
传统的用在网页中的javascript代码文件与文件之中的内容都是全局相互可见的,这对于大型项目特别是多人合作的项目来说挺不好的,容易互相影响,出现各种问题。
大多数编程语言都有类库、模块等概念来处理这个问题,在Node.js中早期是使用CommonJS规范来处理这个问题。后来JavaScript的ES6标准中制定了ES module规范,所以Node.js也渐渐的开始支持ES module规范了。
这篇文章将对这两种模块化方式做个简单的使用说明。
CommonJS
基础使用
CommonJS规范模块使用上主要就三点:
- 每个文件就是一个模块,有自己的作用域,其中的变量、函数等都是私有的,对其他文件不可见;
- 可以使用
module.exports
导出当前模块中的变量、函数等,供其它文件使用; - 其它文件中使用
require()
方法引入模块;
下面是个简单的例子:
// common.js
let name = 'naisu'
module.exports.name = name // 导出name变量
module.exports.fun = function(){ // 导出函数
console.log('233~');
}
// test.js
const cm = require('./common.js') // 导入模块
console.log(cm.name)
cm.fun()
上面例子可以看到CommonJS规范模块导入导出使用都很简单。对于模块而言所有要导出的东西整体就是一个对象,挂在module.exports上;其它文件中用 require() 方法导入模块其实就是导入了模块的module.exports上的对象。
module 对象
CommonJS规范中每个模块内部,都有一个module对象代表当前模块,它有以下属性:
module.id 模块的识别符,通常是带有绝对路径的模块文件名;
module.filename 模块的文件名,带有绝对路径;
module.loaded 返回一个布尔值,表示模块是否已经完成加载;
module.parent 返回一个对象,表示调用该模块的模块;
module.children 返回一个数组,表示该模块要用到的其他模块;
module.exports 表示模块对外输出的值;
上面属性中最重要的就是 module.exports
了,就像前面所描述的所有要导出的东西整体就是一个对象,挂在module.exports上。
在Node.js中提供了一个 exports
变量,该变量指向 module.exports
,也就是你可以使用 exports 来代替 module.exports ,但要注意的是不能使用 exports = ...
这种方式赋值,因为 exports 的内部实现其实可以理解为 var exports = module.exports
。
require() 方法
CommonJS规范中 require() 方法用于导入模块,导入放入可以向前面例子中 require('./common.js')
也可以写成 require('./common')
,两者效果是一样的。
require() 导入时可以使用相对路、绝对路径,或者也可以不带路径。在不带路径的情况下,程序运行时会从Node.js安装目录、项目的node_modules文件夹、用户的node_modules文件夹、系统node_modules文件夹等依次寻找该模块。
要注意的是 require() 导入模块生成的是一份拷贝,一旦一个值导出后模块内部的变化就不会影响到这个值了:
使用 require() 导入模块后会缓存该模块,下次再加载该模块会直接从缓存中取出。所有缓存保存在 require.cache
中,可以使用 delete require.cache[moduleName]
方法删除缓存:
上面的 require.resolve()
方法用于将模块解析到绝对路径。
ES Module
在JavaScript的ES6标准中加入了模块功能,通常被成为ES Module(或ES6 Module),这和前面的CommonJS有一个非常大的不同点:CommonJS中的模块只是从代码层面来实现,本质其实是个对象,ES Module是从语言层面制定的,原生的功能在性能、调试、编译等方面都有优势。
目前Node.js(v14.17.6)中虽然已经支持ES Module,但是使用起来还不是很舒服。想要使用ES Module的话得稍作处理,主要有两种方式(二选一):
- ES模块和使用ES模块模块的文件名后缀都改为
.mjs
; - 项目文件夹中建立
package.json
文件,设置其中type字段{ "type": "module" }
,注意使用该方式后CommonJS模块文件名后缀就得改为.cjs
了;
ES Module使用上和CommonJS差不多:
- 每个文件就是一个模块;
- 使用
export
导出; - 使用
import
导入;
下面是个简单的例子:
// module.mjs
let name = 'naisu'
function fun() {
console.log('fun 233~')
}
class MC {
constructor() {
console.log('MC 233~')
}
}
export { name, fun, MC }
// 下面代码等同于上面
// export let name = 'naisu'
// export function fun() {
// console.log('fun 233~')
// }
// export class MC {
// constructor() {
// console.log('MC 233~')
// }
// }
// test.mjs
import { name, fun, MC } from './module.mjs'
console.log(name)
fun()
let mc = new MC()
ES Module中 import 可以使用 *
代表模块整体、使用 as
来重命名,所以可以有下面用法:
// module.mjs
let name = 'naisu'
function fun() {
console.log('fun 233~')
}
export { name, fun}
// test.mjs
import * as esm from './module.mjs'
console.log(esm.name)
esm.fun()
模块在输出的时候可以有一个默认输出,使用 export default
修饰,可以用来修饰匿名函数。在import这个默认输出的时候需要指定一个名字。参考下面例子:
// module.mjs
let name = 'naisu'
function fun() {
console.log('fun 233~')
}
export { name }
export default fun
// test.mjs
import ff, {name} from './module.mjs'
console.log(name)
ff()
ES Module中导出与导入是动态绑定的关系,通过相关的接口还是可以修改内部的值的。这点和 CommonJS 有区别。
上面用 import 导入模块是静态的,会在程序运行之前导入,其实还有一个动态导入的方法 import()
,这个是异步的方法,调用时将返回一个 Promise 。使用方式可以如下:
import('./module.mjs').then((module) => {
// TODO
});
// 也可以使用await关键字
let module = await import('./module.mjs');
混合使用
CommonJS 中使用 ES Module 可以使用 import() 方法导入:
ES Module 中使用 CommonJS 也只能用 import 方式整体导入:
更多内容可以可以参考下面文章:
阮一峰的网络日志 《Node.js 如何处理 ES6 模块》
总结
总的来说目前Node.js中的模块使用 CommonJS 规范还是最方便的,ES Module 虽然是JavaScript原生标准,但现在在Node.js中使用并不是非常顺畅,不过可以预见的是原生的 ES Module 将来大概率会完全替代现有的 CommonJS 的。
以上是关于Node.js入门 03:模块化规范 CommonJS 与 ES Module的主要内容,如果未能解决你的问题,请参考以下文章