CommonJS
现在写代码真的是越来越方便了,什么都可以分开写,我们就来看看模块化开发的一种方式吧。
什么是CommonJS
Node的应用由模块组成,采用的是CommonJs模块规范。
CommonJS模块的特点如下所示:
每个文件都是一个模块,有自己的作用域,不会污染全局作用域
模块可以多次加载,但是只会在加载时候运行一次,结果会被缓存,如果需要重新运行,则需要清除缓存
模块加载的顺序是按照其在代码中的顺序
module对象
每一个模块都有一个module对象,代表当前模块。
module.id模块的标识符,通常是带有绝对路径的模块文件名module.filename模块文件名,带有绝对路径module.loaded布尔值,表示该模块是否已经完成加载module.parent对象值,表示调用该模块的模块module.children,返回一个数组,表示该模块要用到的其他模块modele.exports,表示模块对外输出的值
module.exports属性
表示模块对外输出的接口,其他文件加载该模块就相当于读取的是module模块。
我们可以直接给module.exports赋值,表示我们要传出的方法或者变量是什么。
exports变量
Node为每一个模块都提供了一个exports变量,指向的是module.exports。
相当于在每一个模块头部都有着这样的一个命令。
var exports=module.exports;
exports我们不能直接对其进行赋值操作,因为这样会改变exports的指向,从而切断与module.export之间的联系。
只能对其对象赋值:
exports.add=add;
export.hello=function(){
return 'hello'
}
我们为了区分它们一般决定不使用exports。
require命令
Node使用CommonJS模块规范,我们可以通过require命令用于加载模块文件。
require命令的基本功能是读入并执行一个JS文件,然后返回该文件的exports对象。没有发现指定模块的话,就会报错。
//example.js
var message='hi!';
module.exports=function(){
console.log(message);
}
//other.js
require('./example.js')(); // hi!
缓存机制
第一次加载模块的时候,Node会缓存该模块,以后再加载该模块,就直接从缓存中读取。
如果想删除缓存,可以使用以下命令:
//删除指定模块的缓存
delete reqire.cache[moduleName];
//删除所有模块的缓存
Object.keys(require.cache).forEach(function(key){
delete require.cache[key];
})
加载机制
CommonJS的模块加载机制是,输入的值是输出的值的拷贝,一旦输出一个值,模块内部的变化就无法影响到这个值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
上面代码说明,counter输出以后,lib.js模块内部的变化就影响不到counter了。
require的内部处理流程
require命令是CommonJS规范之中,用来加载其他模块的命令。它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load。
Module._load = function(request, parent, isMain) {
// 1. 检查 Module._cache,是否缓存之中有指定模块
// 2. 如果缓存之中没有,就创建一个新的Module实例
// 3. 将它保存到缓存
// 4. 使用 module.load() 加载指定的模块文件,
// 读取文件内容之后,使用 module.compile() 执行文件代码
// 5. 如果加载/解析过程报错,就从缓存删除该模块
// 6. 返回该模块的 module.exports
};
上面的第4步,采用module.compile()执行指定模块的脚本,逻辑如下。
Module.prototype._compile = function(content, filename) {
// 1. 生成一个require函数,指向module.require
// 2. 加载其他辅助方法到require
// 3. 将文件内容放到一个函数之中,该函数可调用 require
// 4. 执行该函数
};上面的第1步和第2步,require函数及其辅助方法主要如下。
require(): 加载外部模块require.resolve():将模块名解析到一个绝对路径require.main:指向主模块require.cache:指向所有缓存的模块require.extensions:根据文件的后缀名,调用不同的执行函数
一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。
(function (exports, require, module, __filename, __dirname) {
// YOUR CODE INJECTED HERE!
});Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值。
参考——阮一峰老师的博客