本文深入解析Node.js中CommonJS规范的exports与module.exports机制,阐明二者指向同一内存对象的关系及导出差异。同时对比ES6模块系统,详细区分export与export default在数量、语法及导入方式上的不同,通过代码示例展示如何正确导出常量、函数及默认值,帮助开发者理清模块加载逻辑。
Node.js模块系统详解:exports、module.exports与ES6 export机制解析
前言
近期难得有空闲时间,我决定重新系统地学习Node.js编程。在研究模块引入时,我发现Node.js中常用require方式,这让人联想到ES6标准中的各种export和export default语法。
面对这些概念,确实容易让人感到困惑和头疼。
理清这些概念后,我们需要先明确它们各自适用的范围。
- require:Node.js环境和ES6环境均支持的引入方式
- export / import:仅ES6标准支持的导出与引入机制
- module.exports / exports:仅Node.js环境支持的导出方式
为了不再混淆,有必要彻底厘清它们之间的关系。接下来我们将深入探讨具体实现。
Node模块机制:CommonJS规范与exports关系
Node模块机制
Node.js内部的模块系统遵循CommonJS规范。
那么,什么是CommonJS规范呢?
早期JavaScript代码缺乏统一的模块概念,导致代码混乱。CommonJS规范的出台正是为了解决这一问题,对模块进行了明确定义。
CommonJS规范将模块划分为三个核心部分:模块标识(module)、模块定义(exports)以及模块引用(require)。
首先解释exports和module.exports的关系。
当Node.js执行一个文件时,会在该文件内部生成exports和module两个对象。
其中,module对象包含一个exports属性。它们之间的关系如图所示,两者最初指向同一块内存区域。
exports = module.exports = {};

接下来我们通过代码来验证这一机制。
//utils.js
let a = 100;
console.log(module.exports); //能打印出结果为:{}
console.log(exports); //能打印出结果为:{}
exports.a = 200; //这里辛苦劳作帮 module.exports 的内容给改成 {a : 200}
exports = '指向其他内存区'; //这里把exports的指向指走
//test.js
var a = require('/utils');
console.log(a) // 打印为 {a : 200}
从上述分析可以看出,require导出的内容实际上是module.exports所指向的内存块内容,而非exports本身。
简而言之,exports只是module.exports的一个引用,用于辅助后者添加内容。
用通俗的话讲,exports仅辅助module.exports操作内存中的数据。尽管对exports进行了各种赋值操作,最终被require导出的内容依然取决于module.exports的指向。
只要理解内存块的概念,就能清晰掌握这一机制。
为了避免混淆,建议尽量统一使用module.exports进行导出,并使用require进行导入。
ES模块的导出与导入:export与export default区别
ES模块的导出与导入
说实话,ES6中的模块系统相对清晰,但仍有一些细节需要注意。
例如export和export default的区别,以及导入时的语法差异,如import a from ...与import {a} from ...。这些细节容易让人困惑,下面我们将逐一厘清。
export与export default的区别
首先讲解这两种导出方式,并阐述它们的具体区别。
- export和export default均可用于导出常量、函数、文件或模块。
- 在一个文件或模块中,export和import可以有多个,但export default仅允许有一个。
- 通过export方式导出时,导入时必须使用大括号{}包裹;而使用export default导出时,则不需要大括号。
- export可以直接导出变量表达式,而export default不支持直接导出变量表达式。
接下来我们通过代码来验证这些结论。
testEs6Export.js
'use strict'
//导出变量
export const a = '100';
//导出方法
export const dogSay = function(){
console.log('wang wang');
}
//导出方法第二种
function catSay(){
console.log('miao miao');
}
export { catSay };
//export default导出
const m = 100;
export default m;
//export defult const m = 100;// 这里不能写这种格式。
index.js
//index.js
'use strict'
var express = require('express');
var router = express.Router();
import { dogSay, catSay } from './testEs6Export'; //导出了 export 方法
import m from './testEs6Export'; //导出了 export default
import * as testModule from './testEs6Export'; //as 集合成对象导出
/* GET home page. */
router.get('/', function(req, res, next) {
dogSay();
catSay();
console.log(m);
testModule.dogSay();
console.log(testModule.m); // undefined , 因为 as 导出是 把 零散的 export 聚集在一起作为一个对象,而export default 是导出为 default属性。
console.log(testModule.default); // 100
res.send('恭喜你,成功验证');
});
module.exports = router;
从上述代码可以看出,ES6的模块系统确实非常灵活。
代码地址
GitHub仓库:https://github.com/XuXiaoGH/exportImportTest
本地下载:http://xiazai.jb51.net/201710/yuanma/exportImportTest(jb51.net).rar
参考文献
1.老树新芽,在ES6下使用Express
2.exports 和 module.exports 的区别 3.module.exports与exports,export与export default之间的关系
3.module.exports与exports,export与export default之间的关系
感谢这三位前辈的分享。
总结