什么是模块

在 node 中模块与文件是一一对应的,也就是说一个 node 文件就是一个模块;

模块文件类型

node可以加载的文件类型有三种: .js, .json, .node,在加载的时候可以省略后缀名;

  • .js 就是常规的 javascript 文件;

  • .json 则是 json 格式对象,这两者(.js.json)加载的时候被视为 javascript 动态编译和处理使用;

  • .node 则是预编译好的插件模块;

简单示例说明一下模块的加载

var http     = require('http');
//加载全局模 块http
var mymodule = require('./router/mymodule');
//加载与当前目录同级的 router 目录下的 mymodule.js 模块
var common   = require('/home/reg/Projects/Test/common');
//加载绝对路径下查找到的 common.js 模块

上面的例子说明了三种加载模块的方式

  • 通过 require('moduleName'), 加载的是node提供的核心模块;

  • 通过 ./../, 则按照相对路径从当前文件所在文件夹开始寻找模块;

  • 通过 /, 则以系统根目录开始寻找模块;

核心模块与文件模块

这里我们再解释一下当模块中不带有 ./../ 时会怎么去找:

首先,node 会最优先加载核心模块,所谓的核心模块存在于 node 安装目录下的 lib 目录里,他们的加载是在 node 容器初始化的时候就加载完成的,不需要在加载的过程中进行编译,例始 http 模块,而且这些模块也不会被覆盖;

如果加载的不是核心模块,则会从当前目录寻找 node_modules 里面的同名模块;如果当前目录没有该模块,则往上一级目录寻找 node_modules 中的同名模块;一直找到系统根目录的 node_modules 为止,如果还是没有则抛出一个错误:
Error: Cannot find module 'moduleName'

上一个例子看一下:

/
|_ node_modules
|          |_ common.js
|_ app
       |_ node_modules
       |           |_ http.js
       |           |_ hello.js
       |_ index.js
  • 如果 index.js 里面有 require('hello'), 则加载的将会是 /app/node_modules/hello.js 这个文件;

  • 如果 index.js 里面有 require('common'), 则加载的将会是 /node_modules/common.js 这个文件;

  • 如果 index.js 里面有 require('http'), 则加载的将会是核心模块的 http,而不是 /app/node_modules/http.js, 因为核心模块不允许被重写;如果想要加 /app/node_modules/http.js 可以使用路径 (require('./node_modules/http')) 的方式寻找;

目录模块

除了上述两种模块以外, node 还允许使用目录模块;

例如:

/ app
       |_ myModules
       |           |_ index.js
       |_ index.js

如果,我们在 index.js 中引入了 require('./myModules'), 则会加载 /app/myModules/index.js

所以,如果我们引入的是一个目录而不是一个文件, node 会尝试加载这个目录下的 index.jsindex.json文件;

但是, 如果在 myModules 的目录下还有一个叫做 package.json 的文件,那么我们就可以改变这一默认模块加载行为;在这个文件里面可以保存一个 json 对象,而里面最关键的是一个属性 main, 可以让我们定义这个目录下的哪个文件是模块的主文件。如下:

/app
       |_ myModules
       |           |_ package.json
       |           |_ myname.js
       |           |_ index.js
       |_ index.js

其中 package.json 有如下定义:

{main: "./myname.js"}

这个时候,则会加载 /app/myModules/myname.js这个文件咯;

当然,package.json 可不只是单单能够做这么简单的事情,里面还可以定义很多像依赖管理之类的事情,这个就过于高级了,以后再聊;

模块缓存

对于 node 来说,主进程加载过一次的模块,就会被永久缓存起来,即使后来又修改过该模块的内容,然后再在主进程尝试调用 require, 进行重新加载,但是这样其实拿到的还是第一次加载的那个对象;如果想要拿到新的内容,就必须重启 node, 或通过删除保存在 require.cache 中的模块缓存对象。

暴露模块属性

如果,我们引入一个模块,其实 node 就是简单地在当前环境调用某个模块文件并解释一遍,如:

//hello.js
console.log('Hello World!');

//test.js
var hello = require('./hello');
console.log(hello);

$ node test
Hello World!
{}

通过执行结果,我们发现直接打印出 Hello World!, 而第二行打印一个空对象;

如果,希望暴露模块中的一些方法或对象供外部调用者使用,那么我们可以给 module.exports 赋值,来实现;如:

//hello.js
module.exports={
    "name":"Justice",
    "myName":function(){
        return this.name;
    }
}

//test.js
var hello = require('./hello');
console.log(hello);

$ node test
{ name: 'Justice', myNmae: [Function] }

通过执行结果,我们发现输出了一个 { name: 'Justice', myNmae: [Function] } 对象;

值得注意的是 exports 的定义必须是在主进程里面完成,不可以延迟加载,即以下代码中 setTimeout 部分无效:

setTimeout(function(){
    module.exports = {"a":"Test"};
},50);

最后,还要注意一个模块中最后一次赋值的 module.exports 生效,也就是说最好是一个模块中最好是只在最后给 module.exports 赋值一次;

在 Node, require一个文件实际上是在 require 这个文件定义的模块,所有的模块都拥有一个隐式 module 对象,当我们调用 require 时,实际上返回的是 module.exports;