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 不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成