浅谈 CommonJS 模块化规范

在浅谈 CommonJS 模块化规范之前,先看看模块化有啥好处:

  • 方便代码维护
  • 避免命名冲突
  • 避免全局环境污染
  • 还没想好...

概述

CommonJS 规范是 NodeJS 使用的一套模块化规范,具有以下特点:

  • 一个文件就是一个模块
  • 所有代码都运行在模块作用域
  • 模块只会在第一次加载时运行一次,然后运行结果就被缓存,再次加载会直接读取缓存结果。要想让模块再次运行,必须清除缓存
  • 模块加载的顺序,按照其在代码中出现的顺序

规范

CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性

// example.js
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

上面代码通过 module.exports 输出变量 x 和函数 addX

require 方法用于加载模块

var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6

require 的加载规则

require 的加载规则如下:

(1)如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。比如 require('/home/marco/foo.js') 将加载 /home/marco/foo.js

(2)如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。比如,require('./circle') 将加载当前脚本同一目录的 circle.js

(3)如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node 的系统安装目录中),或者一个位于各级 node_modules 目录的已安装模块(全局安装或局部安装)

举例来说,脚本 /home/user/projects/foo.js 执行了 require('bar.js') 命令,Node 会依次搜索以下文件

/usr/local/lib/node/bar.js
/home/user/projects/node_modules/bar.js
/home/user/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js

(4)如果参数字符串不以“./“或”/“开头,而且是一个路径,比如 require('example-module/path/to/file'),则将先找到 example-module 的位置,然后再以它为参数,找到后续路径

(5)如果指定的模块文件没有发现,Node 会尝试为文件名添加.js、.json、.node后,再去搜索

(6)如果想得到 require 命令加载的确切文件名,使用 require.resolve() 方法

删除缓存

上文说过,模块只会在第一次加载时运行一次,然后运行结果就被缓存,再次加载会直接读取缓存结果。要想让模块再次运行,必须清除缓存

所有缓存的模块保存在 require.cache 之中,如果想删除模块的缓存,可以像下面这样写

// 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})

注意,如果同样的模块名,但是保存在不同的路径,require 命令还是会重新加载该模块

可以但不推荐之 global

CommonJS 规范中每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见,如果想在多个文件分享变量,必须定义为 global 对象的属性

// example.js
global.warning = true;

// main.js 
console.log(global.warning) // true

可以但不推荐之 exports 变量

为了方便,Node 为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令

var exports = module.exports;

造成的结果是,在对外输出模块接口时,可以向 exports 对象添加属性和方法

exports.pi = Math.PI;
exports.area = function (r) {
  return Math.PI * r * r;
};

注意,不能直接将 exports 变量指向一个值,因为这样等于切断了exports 与 module.exports 的联系

exports = function (x) {
  console.log(x)
};

上面这样的写法是无效的,因为exports不再指向 module.exports 了

下面的写法也是无效的

exports.hello = function() {
  return 'hello';
};

module.exports = 'Hello world';

上面代码中,hello 函数是无法对外输出的,因为 module.exports 被重新赋值了

exports 与 module.exports 容易混淆,建议放弃使用 exports,只使用 module.exports

除特殊说明外本人博客均属原创,转载请注明出处:http://blog.johnhan.cn/blog_1067.html
京ICP备19044523号-1