Node.js 模块系统详解:CommonJS 与 ESM 语法对比及混用处理

作者:夜航船时间:2026-10-23 05:18:35浏览:635

本文深入解析Node.js中CommonJS与ES6 Module(ESM)两种模块系统的语法差异。详细对比module.exports与export、require与import的使用场景及关键区别,重点说明ESM加载CommonJS模块时的动态绑定限制及解决方案,帮助开发者避免模块引用错误。

Node.js 模块规范演进

社区模块规范概述

1. CommonJS规范

规范实现者:

NodeJS服务端环境

Browserify浏览器环境

2. AMD规范(全称:异步模块定义)

规范实现者:

RequireJS浏览器环境

3. CMD规范(通用模块定义)

规范实现者:

seaJS(服务端与浏览器通用)

官方模块规范

1. ESM规范(即ES6 Module)

各浏览器和服务端环境

目前常用方案包括浏览器端的RequireJS、NodeJS以及ESM

CommonJS 模块语法详解

CommonJS语法分析

module.exports机制

关键点说明

1. module.exports实质上是一个对象,最终模块导出的内容即为该引用指向的对象


 module.export.key = value
 // eg: 
 module.export.a = 1; 
 // 整体管理导出,此时exports与module.exports指向断开,导致exports上的属性被忽略
 module.export = {
 a:1,
 b:1
 }

2. exports是module.exports的辅助变量,用于就地导出;两者默认指向同一对象,即 module.exports === exports 为 true


 // 像比较长的程序,写完所有之后,再去找到需要导出的变量再移到低端的export.module上逐个添加,是相当麻烦,一般在变量下决定是否导出
 //eg 
 let fA = function () {

 }
 module.exports.fA = fA
 let fB = function () {

 }
 module.exports.fB = fB

 // exports 简洁很多
 let fA = function () {

 }
 module.fA = fA
 let fB = function () {

 }
 module.fB = fB

3. 逐个导出时使用exports,导出整个对象时使用module.exports;不建议混用,若需混用可进行如下处理:


 // 在最后导出时将exports对象和module.exports对象合并
 exports.a = 1
 module.exports = Object.assign({
 b : 1
 },exports)

require机制

关键点说明

1. 模块区分,了解即可

let f = require('url')


 // 核心模块,第三方模块(npm安装),指定模块名即可
 let f = require ('modulename')
 // 自定义模块,需指定相对或者绝对路径
 let f = require('absolutePATH/relativeAPTH')

2. 查找时,若未指定后缀名,会尝试追加.js、.json、.node后缀,此处可简化操作


 let f = require('./circle')
 // 等同于
 let f = require('./circle.js')

ESM (ES6 Module) 语法解析

ESM语法分析

export var/function/class声明

关键点说明

1. 导出值需与内部值存在对应关系,即:


 // error
 export 1

 // error
 let m = 1
 export m

 // correct
 export let m = 1

 // correct
 let m = 1;
 export {m}

直接在声明时导出,或使用{}包裹后导出

2. export default用于导出默认值,使用户无需知晓内部变量名即可使用

PS:一个模块仅允许存在一个默认导出


 export default 等同 export {add as default}
 // 不能接变量声明语句
 export default let a = 1

普通导出与默认导用的区别


 // 普通导出的导入使用
 export {f}
 import {f} from 'fmod'

 // 默认导出的导入使用,可以无需知道导出模块的内部变量名,任意重命名
 export default f
 import c from 'fmod'

3. 导出值与模块内部值是动态绑定的


export let foo = 'bar';
// 500毫秒后,其他模块拿到的foo值会变成bar
setTimeout(() => foo = 'baz', 500);

import用法解析

关键点说明

1. 导入非默认变量时需使用{}语法,且变量名必须与导出时一致


 // 普通导出的导入使用
 export {f}
 import {f} from 'fmod'

2. 导入默认变量时省略{},并可对导出变量进行重命名


 // 默认导出的导入使用,可以无需知道导出模块的内部变量名,任意重命名
 export default f
 import c from 'fmod'

3. 可使用*导入整个模块


 // circle.js
 export let radius = 1
 export let area = 2*PI*radius
 // main.js
 import * as circle from './circle.js'
 circle.radius
 circle.area

4. 可使用,同时导入默认导出和常规导出


import _,{each, forEach} from 'lodash'

5. 多次import同一模块,仅执行一次

6. import可与require混用,但import在静态分析阶段执行,即先于require加载,需注意导入顺序要求


export { foo, bar } from 'my_module';

// 可以简单理解为,但是合并写法等同于没有在当前模块中导入my_module,即无法使用
import { foo, bar } from 'my_module';
export { foo, bar };

ESM 加载 CommonJS 模块的处理机制

ESM加载CommonJS模块

关键点说明

1. CommonJS模块输出的module.exports对象将被Node.js转换为默认导出


 // 导出
 export default module.exports
 // 导入,类似导入默认
 import m from './m'

2. 此时CommonJS模块变量遵循其原有规则,不具备动态绑定特性


 module.exports = 123;
 setTimeout(() => module.exports = null,500)
 //500毫秒后,module.exports仍然是123

3. 由于CommonJS模块在运行时才确定输出的module.exports对象,而ESM在编译时即确定接口,因此导入时不允许使用解构语法


 import {readFile} from 'fs'

解决方案


 //整体导入 
 import * as express from 'express'
 const app = express.default()
 
 //默认导入,更优
 import express from 'express'
 const app = express() 

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对本站的支持。

作者最新文章