模块化开发--CommonJS

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函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括requiremoduleexports,以及其他一些参数。

(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE INJECTED HERE!
});

Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值。

参考——阮一峰老师的博客