CommonJS 作为 Node 端模块导入的事实方案,与 ES 标准提出的 ES Module 有何差异呢?
一、CommonJS 和 ESM 都可以对引入的对象进行赋值
1.1 CommonJS 模块
<!--- 定义入口文件 main.js --->
const { a } = require('./test')
setTimeout(() => {
console.log(a.value)
}, 200)
console.log(a.value)
<!--- 定义模块文件 test.js --->
const a = { value: 1 }
setTimeout(() => {
a.value = 2
}, 100)
module.exports = { a }
输出: 1, 2
1.2 ESM 模块
<!--- 定义入口文件 main.mjs --->
import { a } from './test.mjs'
setTimeout(() => {
console.log(a.value)
}, 200)
console.log(a.value)
<!--- 定义模块文件 test.mjs --->
let a = { value: 1 }
setTimeout(() => {
a.value = 2
}, 100)
export { a }
输出: 1, 2
1.3 动态 ESM
<!--- 定义入口文件 main.mjs --->
const { a } = await import('./test.mjs')
setTimeout(() => {
console.log(a.value)
}, 200)
console.log(a.value)
<!--- 定义模块文件 test.mjs --->
let a = { value: 1 }
setTimeout(() => {
a.value = 2
}, 100)
export { a }
输出: 1, 2
二、CommonJS 模块输出的是一个值的拷贝,ESM 模块输出的是值的引用
2.1 CommonJS 模块
<!--- 定义入口文件 main.js --->
const { a } = require('./test')
setTimeout(() => {
console.log(a)
}, 200)
console.log(a)
<!--- 定义模块文件 test.js --->
let a = 1
setTimeout(() => {
a = 2
}, 100)
module.exports = { a }
输出: 1, 1
2.2 ESM 模块
<!--- 定义入口文件 main.mjs --->
import { a } from './test.mjs'
setTimeout(() => {
console.log(a)
}, 200)
console.log(a)
<!--- 定义模块文件 test.mjs --->
let a = 1
setTimeout(() => {
a = 2
}, 100)
export { a }
输出: 1, 2
2.3 动态 ESM
<!--- 定义入口文件 main.mjs --->
const { a } = await import('./test.mjs')
setTimeout(() => {
console.log(a)
}, 200)
console.log(a)
<!--- 定义模块文件 test.mjs --->
let a = 1
setTimeout(() => {
a = 2
}, 100)
export { a }
输出: 1, 1
三、ES6 Module 只存只读,不能改变其值,指针指向不能变
import * as a from './test.mjs'
a = 2 // throw a error
import 导入的值类似 const 定义,值不能改变
四、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
五、CommonJS 模块的 require()是同步加载模块,ES6 模块的 import 命令是异步加载
const { a } = await import('./test.mjs')
六、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ESM 不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成