Node.js模块机制详解:核心模块、文件模块与exports/module.exports区别
本文深入解析Node.js模块系统,涵盖核心模块与文件模块的加载优先级及路径规则。重点对比exports与module.exports的区别,通过代码示例阐明两者在对象引用与赋值时的差异,并解释模块缓存机制对性能的影响。最后介绍module对象的常用属性,帮助开发者规范编写Node.js模块。
Node.js模块概述
简介及资料
查阅Node.js官方API文档(http://nodejs.org/api/)可知,Node.js内置了大量核心模块。这些核心模块通常已编译为二进制文件,开发者可直接通过require('模块名')进行调用。核心模块拥有最高的加载优先级,当自定义模块与核心模块同名时,核心模块会被优先加载。
(本次主要说自定义模块)
除了核心模块,Node.js还支持文件模块。这类模块可以是JavaScript代码文件(后缀为.js)、JSON格式文本文件(后缀为.json),或者是经过编译的C/C++扩展文件(后缀为.node)。
访问文件模块需使用require函数,并指定路径。路径可以是绝对路径(以"/"开头)、相对路径(以"./"或"../"开头)。其中,"./"表示当前同级目录,"../"表示上级目录。在require调用中,文件后缀名可以省略。
当省略文件后缀时,Node.js会按照特定的优先级尝试加载文件,顺序为:.js文件 > .json文件 > .node文件。
创建与调用自定义模块
创建一个自定义模块
以一个计数器为例

代码如下:
var outputVal = 0; //输出值
var increment = 1; //增量
/* 设置输出值 */
function seOutputVal (val) {
outputVal = val;
}
/* 设置增量 */
function setIncrement(incrementVal){
increment = incrementVal;
}
/* 输出 */
function printNextCount()
{
outputVal += increment;
console.log(outputVal) ;
}
function printOutputVal() {
console.log(outputVal);
}
exports.seOutputVal = seOutputVal;
exports.setIncrement = setIncrement;
module.exports.printNextCount = printNextCount;
自定义模块 示例源码
示例中重点在于exports和module.exports;提供了外部访问的接口,下面调用一下看看效果吧
调用自定义模块

代码如下:
/*
一个Node.js文件就是一个模块,这个文件可能是Javascript代码、JSON或者编译过的C/C++扩展。
重要的两个对象:
require是从外部获取模块
exports是把模块接口公开
*/
var counter = require('./1_modules_custom_counter');
console.log('第一次调用模块[1_modules_custom_counter]');
counter.seOutputVal(10); //设置从10开始计数
counter.setIncrement (10); //设置增量为10
counter.printNextCount();
counter.printNextCount();
counter.printNextCount();
counter.printNextCount();
/*
require多次调用同一模块不会重复加载
*/
var counter = require('./1_modules_custom_counter');
console.log('第二次调用模块[1_modules_custom_counter]');
counter.printNextCount();
自定义模式调用 源码
运行可以发现通过exports和module.exports对外公开的方法都可以访问!
示例中可以看到,我两次通过require('./1_modules_custom_counter')获取模块,但是第二次引用后调用printNextCount()方法确从60开始~~~
原因是node.js通过requirerequire多次调用同一模块不会重复加载,Node.js会根据文件名缓存所有加载过的文件模块,所以不会重新加载了
注意:通过文件名缓存是指实际文件名,并不会因为传入的路径形式不一样而认会是不同的文件
在我创建的1_modules_custom_counter文件中有一个printOutputVal()方法,它并没有通过exports或module.exports提供对外公开访问方法,
如果1_modules_load文件中直接访问运行会出现什么样的情况呢?
答案是:TypeError: Object #<Object> has no method 'printOutputVal'
exports与module.exports的区别
exports和module.exports 区别
经过上面的例子,通过exports和module.exports对外公开的方法都可以访问!那既然两种都能达到效果,但总得有点区别的吧~~~用个例子看看吧!

代码如下:
var counter = 0;
exports.printNextCount = function(){
counter += 2;
console.log(counter);
}
var isEq = (exports === module.exports);
console.log(isEq);
2_modules_diff_exports.js 文件源码
下面再新建个2_modules_diff_exports_load.js文件调用一下

代码如下:
var Counter = require('./2_modules_diff_exports');
Counter.printNextCount();
调用后,执行结果如上图
我在2_modules_diff_exports_load.js文件中输出了isEq的值 ( var isEq = (exports === module.exports); ),返回的true
PS:注意是三个等号,如果不清楚自已查查资料吧!
不用急着下结论,把这两个JS文件分别改成module.exports对应的代码
代码如下:
//修改后的2_modules_diff_exports.js源码如下
var counter = 0;
module.exports = function(){
counter += 10;
this.printNextCount = function()
{
console.log(counter);
}
}
var isEq = (exports === module.exports);
console.log(isEq);
代码如下:
//修改后的2_modules_diff_exports_load.js文件源码如下
var Counter = require('./2_modules_diff_exports');
var counterObj = new Counter();
counterObj.printNextCount();

调用后,执行结果如上图
我在2_modules_diff_exports_load.js文件中输出了isEq的值 ( var isEq = (exports === module.exports); ),返回的false,这与用先前得到的结果不一致!
PS:不要用Counter.printNextCount();去访问,你只会得到一个错误的提示
API提供了解释
http://nodejs.org/api/modules.html
Note that exports is a reference to module.exports making it suitable for augmentation only. If you are exporting a single item such as a constructor you will want to use module.exports directly instead exports仅仅是module.exports的一个地址引用。nodejs只会导出module.exports的指向,如果exports指向变了,那就仅仅是exports不在指向module.exports,于是不会再被导出
参考其它理解:
http://www.hacksparrow.com/node-js-exports-vs-module-exports.html
http://zihua.li/2012/03/use-module-exports-or-exports-in-node/
module.exports才是真正的接口,exports只不过是它的一个辅助工具。 最终返回给调用的是module.exports而不是exports。 所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是module.exports本身不具备任何属性和方法。 如果,module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。
module.exports覆盖exports的行为
exports和module.exports 覆盖
上面也也基本明白了exports和module.exports的关系和区别,但如果同时针对printNextCount()方法存在exports和module.exports,结果如何?

调用结果

从结果可以看出,并没有报错,表示可以这么定义,但最终module.exports覆盖了exports
虽然结果不会报错,如果这么用开发中难免会有一些问题存在,所以
1.最好别分别定义module.exports和exports
2.NodeJs开发者建议导出对象用module.exports,导出多个方法和变量用exports
module对象的其他属性
其它...
API中还提供了其它的方法,就不细讲了,在上面例子的基础上自已动手一输出就知道了
module.id
返回string类型的模块标识,一般为完全解析后的文件名
module.filename
返回一个string类型的完全解析后文件名
module.loaded
返回一个bool类型,表示是否加载完成
module.parent
返回引用该模块的模块
module.children
返回该模块引用的所有模块对象的数组
macOS卸载Node.js指南:区分Homebrew与pkg安装包的处理方式
2026-11-05 11:28:12
JS逆向入门:浏览器Console调试与Node.js环境搭建指南
2026-11-05 10:57:15
Node.js 大版本升级需要注意哪些破坏性变更
2026-11-03 20:35:44
Node.js官网LTS版与Current版有什么区别
2026-11-03 19:33:49
Windows彻底卸载Node.js:清理AppData残留与PATH环境变量
2026-11-03 19:15:35
