Node.js中的全局对象之module模块对象及模块加载机制

 2015年06月17日    1362     声明


module模块是Node.js全局对象之一,Node.js的模块系统就是由module对象实现的,它负责加载、编译、缓存每个用过的文件。在模块文件中module变量代表对当前模块的引用,模块加载方法require、模块导出对象exports都是由module模块提供的。

  1. module对象
  2. 模块加载


1. module对象

1.1 模块导出对象:module.exports

module.exports对象是通过模块系统产生的,当你希望你的模块文件中方法或对象等能够其它模块所访问,就需要将其导出,也就将要导出的属赋值给module.exports。 使用示例:

a.js文件,代码如下:

var EventEmitter = require('events').EventEmitter;

module.exports = new EventEmitter();

// 实现一个事件发射器。引用本模块会在加载完成1秒后发个一个'ready'事件
setTimeout(function() {
  module.exports.emit('ready');
}, 1000);

在另一个文件中引用有a.js文件:

var a = require('./a');
a.on('ready', function() {
  console.log('收到a模块的 ready 事件');
});


通过module.exports导出的属性必须即时生效。对于如下x.js文件,将不能工作:

setTimeout(function() {
  module.exports = { a: "hello" };
}, 0);

引用x.js文件,并调用导出对象:

var x = require('./x');
console.log(x.a);	//undefined


exportsmodule.exportsexportsmodule.exports的引用,module.exports才是真正的接口,赋值到exports上的属性,最终都会写入到module.exports属性上。全局对象exports在module类中实现,其代码如下:

global.exports = self.exports;


1.2 模块加载方法:module.require(id)

  • id{String} 引用的模块ID,请参考:模块加载
  • Return: {Object} 引用模块的module.exports

module.require(id)提供类似全局对象require(id)方式的模块加载,但module.require(id)只是简单的模块加载。require对象是对module.require()方法的进一步实现。


1.3 模块标识符:module.id

module.id属性用于设置/获取模块的标识符,默认为模块文件全路径及文件名。


1.4 模块文件名:module.filename

module.id属性用于获取模块文件全路径及文件名。


1.5 模块加载完成状态:module.loaded

module.loaded属性用于返回模块的加载完成状态。


1.6 引入这个模块的模块对象:module.parent

module.parent指引入这个模块的模块对象,例如:y.js引入了x.jsx.jsmodule.parent就是y.js。其结构如下:

{ id: '.',
  exports: {},
  parent: null,
  filename: '/Users/liuht/code/itbilu/demo/module/y.js',
  loaded: false,
  children: 
   [ { id: 'ssss',
       exports: {},
       parent: [Circular],
       filename: '/Users/liuht/code/itbilu/demo/module/x.js',
       loaded: false,
       children: [],
       paths: [Object] } ],
  paths: 
   [ '/Users/liuht/code/itbilu/demo/module/node_modules',
     '/Users/liuht/code/itbilu/demo/node_modules',
     '/Users/liuht/code/itbilu/node_modules',
     '/Users/liuht/code/node_modules',
     '/Users/liuht/node_modules',
     '/Users/node_modules',
     '/node_modules' ] }


1.7 模块引入的所有模块对象:module.children

module.children指向这个模块引入的所有模块对象数组。例如:y.js引入了x.jsy.js中的module.children就包括x.js。其结构如下:

[ { id: '/Users/liuht/code/itbilu/demo/module/x.js',
    exports: { a: 'hello' },
    parent: 
     { id: '.',
       exports: {},
       parent: null,
       filename: '/Users/liuht/code/itbilu/demo/module/y.js',
       loaded: false,
       children: [Circular],
       paths: [Object] },
    filename: '/Users/liuht/code/itbilu/demo/module/x.js',
    loaded: true,
    children: [],
    paths: 
     [ '/Users/liuht/code/itbilu/demo/module/node_modules',
       '/Users/liuht/code/itbilu/demo/node_modules',
       '/Users/liuht/code/itbilu/node_modules',
       '/Users/liuht/code/node_modules',
       '/Users/liuht/node_modules',
       '/Users/node_modules',
       '/node_modules' ] } ]


2. 模块加载

Node.js模块加载机制由全局对象module实现,不同类型的模块其加载机制也不尽相同。本站曾整理过一篇关于Node.js模块加载的文章:Nodejs模块加载机制,本文结合module对象再做简单论述。


2.1 核心模块加载

核心模块指Node安装时被编译成二进制的模块,即:Node的核心模块。核心模块被定义在node源代码的lib/目录下。 require()总是会优先加载核心模块。例如,require('http'),即使有http文件模块或NPM安装http模块,总是返回Node的安装时编译的核心HTTP模块。


2.2文件模块加载

对于文件模块,require()会首先按文件名查找。如果没有查找到,Node会添加.js.json后缀名,再尝试加载,如果还是没有找到,最后会加上.node的后缀名再次尝试加载。

对于.js文件,会被解析为Javascript文本文件,.json会被解析为JSON格式的文本文件,.node则会被解析为编译后的插件模块,由dlopen进行加载。

'/'为前缀的模块,表示绝对路径。例如,require('/home/liuht/foo.js') ,加载的是/home/liuht/foo.js这个文件。

模块以'./'为前缀,则路径是相对于调用require()的文件。 即:circle.js必须和foo.js在同一目录下,才能被require('./circle')找到。

当没有以'/'或者'./'来指向一个文件时,模块会在核心模块"或从node_modules文件夹加载的。

当指定路径的文件不存在时,require()会抛出一个code'MODULE_NOT_FOUND'的错误。


2.3 node_modules文件夹中的模块加载

如果require()中的模块名不是核心模块,也没有以'/' '../''./'开头,那么Node会从当前模块的父目录开始,尝试在它的/node_modules文件夹里加载相应模块。

如果没有找到,那么就再向上移动到父目录,直到到达顶层目录位置。

例如,如果对于'/home/ry/projects/foo.js'的文件调用了require('bar.js'),那么node查找的位置依次为:

/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js


2.4 文件夹做为模块加载

Node支持将文件夹做为模块引用,可以把程序和库放到一个单独的文件夹中,并提供单一入口来指向它。有三种方法,使一个文件夹可以作为require()的参数来加载。

1.加载package.json文件: 在文件夹的根目录创建一个package.json的文件,并在其中指定一个main文件做为模块的入口。示例如下:

{ "name" : "some-library",
  "main" : "./lib/some-library.js" }
示例中,如果文件是放在./some-library目录下面,那么require('./some-library')就将会去加载./some-library/lib/some-library.js

2.加载index.jsindex.node文件:如果目录里没有package.json文件,那么Node就会尝试去加载这个路径下的index.js或者index.node文件。例如,如果上面例子中,没有package.json,那么require('./some-library')就将尝试加载下面的文件:

./some-library/index.js
./some-library/index.node


2.5 模块缓存

模块在第一次加载后会被缓存,因此,每次调用require('foo')的时候都会返回同一个对象。但是多次调用require('foo'),模块中的代码不一定会执行多次,借助这一特性, 可以返回部分完成的对象。如果需要一个模块多次执行,就要输出一个函数,通过调用这个函数实现模块代码的多次执行。