跟着文档学Node(三):CommonJS模块

403 阅读3分钟

本系列文章的目的是在结合文档的前提下深入理解Node.js的各个稳定模块。

在学习的过程中遵循what -> why -> how的思路,即了解该模块是什么、为什么要有这个模块(有什么作用)、怎么去使用该模块的api。

系列文章:

跟着文档学Node(一):Stream

跟着文档学Node(二):Buffer

What: CommonJS模块是什么?

在JS大规模发展后,随着JS应用变得越来越复杂,对模块化的需求也变得越来越大。在ES6的模块标准出炉前,社区出现了各种各样的模块规范:AMD、CMD和CommonJS规范。

Node.js采用了其中的CommonJs规范,也使得CommonJS规范称为目前JS社区的主流选择。

Why: 为什么要有模块规范?

对于复杂系统来说,模块化可以解耦系统的各个功能。对于工程维护来说属于必不可少的。

How

模块的主要功能就是两部分:如何引入、如何导出。

require

require方法是Node.js导入模块的方法。

require本质上是Node.js实现的一个全局私有函数,用来引入其他CommonJS模块。既然作为全局函数,那么它和普通函数一样也是运行时执行,可以在代码中任意地方使用。

而es6 module的import是编译时执行,因此其import必须放在文件开头。

require 寻找路径

require方法支持引用核心模块、具体文件路径、目录,一图搞定引用查找顺序:

image.png

缓存

模块在第一次加载后会被缓存。所以如果调用require解析到同一文件,则返回相同的模块对象。模块本身不会被执行多次。

需要注意的是模块缓存是基于其解析的文件名进行缓存。也就是require('./foo')和require('./Foo')会返回两个模块对象,并不会命中缓存。

模块作用域

在Node.js中,每一个模块的代码都会被封装在一个立即执行函数中:

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});

这也是为什么Node.js中的var变量不会提升到全局。我们可以理解模块导出就是给立即执行函数的module参数赋值。

module.exports 和 exports

在Node.js中我们可以使用module.exports和exports来导出模块中的私有变量。

那么这两种导出方式有什么区别呢?

先下结论:对于引用方来说,require引用的是导出方模块module的exports属性,即module.exports。exports只是对于module.exports的引用。

而Node.js在初始化模块时,会做这么一件事:

module.exports = {};
exports = modules.exports;

因此我们使用exports.name = name 去导出name变量时相当于module.exports.name = name。

相当于exports和module.exports是引用同一个变量,而引用方只能看到module.exports。

一旦我们修改exports变量引用的内存地址,也就是重新赋值一个新对象,那么exports上的属性修改就不能影响到module.exports了,也就无法导出了。

总结

在ES6推出ECMAScript模块化的规范后,它自然成为了未来JS语言模块化规范的标准。

虽然目前我们使用ES6的import/export语法时大都需要使用babel转换为CommonJS的require语法。但是我们还是要拥抱未来,尽量地使用ES6的模块规范。