首页 > 代码库 > NODE.JS API —— Modules(模块)
NODE.JS API —— Modules(模块)
中文参考:http://nodeapi.ucdok.com/#/api/,http://blog.sina.com.cn/oleoneoy
目录
○ Cycles
○ Core Modules
○ File Modules
○ Loading from node_modules Folders
○ Folders as Modules
○ Caching
○ The module Object
○ All Together...
模块
稳定性:5 - 锁定
1 var circle = require(‘./circle.js‘);2 console.log(‘The area of a circle of radius 4 is ‘3 + circle.area(4));
circle.js 的内容如下:
1 var PI = Math.PI;2 3 exports.area = function(r) {4 return PI * r * r;5 };6 7 exports.circumference = function(r) {8 return 2 * PI * r;9 };
模块 circle.js 导出了函数 area() 和 circumference()。想把函数和对象添加到你的模块的根节点,你可以把它们添加到特殊的 exports 对象中。
模块的局部变量是私有的,好像模块被一个函数包裹起来一样。在这个例子里面,变量 PI 是 circle.js 私有的。
如果你想你的模块的导出的根节点是一个函数(例如是一个构造函数)或者如果你想在一次指派就能导出一个完整的对象而不是每次都新建一个属性,把它指派到 module.exports 而不是 exports 中。
在下面,bar.js 使用了一个导出一个构造函数的 square 模块:
1 var square = require(‘./square.js‘);2 var mySquare = square(2);3 console.log(‘The area of my square is ‘ + mySquare.area());
square 模块定义在 square.js 中:
1 // assigning to exports will not modify module, must use module.exports2 module.exports = function(width) {3 return {4 area: function() {5 return width * width;6 }7 };8 }
模块系统的实现在 require("module") 模块中。
Cycles(循环引用)
1 console.log(‘a starting‘);2 exports.done = false;3 var b = require(‘./b.js‘);4 console.log(‘in a, b.done = %j‘, b.done);5 exports.done = true;6 console.log(‘a done‘);
b.js :
1 console.log(‘b starting‘);2 exports.done = false;3 var a = require(‘./a.js‘);4 console.log(‘in b, a.done = %j‘, a.done);5 exports.done = true;6 console.log(‘b done‘);
main.js :
1 console.log(‘main starting‘);2 var a = require(‘./a.js‘);3 var b = require(‘./b.js‘);4 console.log(‘in main, a.done=%j, b.done=%j‘, a.done, b.done);
当 main.js 加载 a.js,然后 a.js 转而加载 b.js。在此时,b.js 师徒加载 a.js。为了防止无限的循环,a.js 的 exports 对象的一个未完成的拷贝返回给 b.js 模块。然后 b.js 完成加载,并将它的 exports 对象提供给 a.js 模块。
当 main.js 加载完所有的模块时,这些模块都完成了完整的加载。程序的输出因此将是:
$ node main.jsmain startinga startingb startingin b, a.done = falseb donein a, b.done = truea donein main, a.done= true, b.done=true
如果你在你的程序中有模块的循环依赖,确保有依据地组织。
Core Modules(核心模块)
如果核心模块的模块标识符被传入 require() ,它们总是优先加载。举个例子, require(‘http‘) 将总是返回内建的 HTTP 模块,即使有一个同名的文件。
File Modules(文件模块)
Loading from node_modules Folders(从 node_modules 文件夹加载)
- /home/ry/projects/node_modules/bar.js
- /home/ry/node_modules/bar.js
- /home/node_modules/bar.js
- /node_modules/bar.js
Folders as Modules(文件夹作为模块)
第一种是在文件夹的根目录创建一个指定 main 模块的 package.json 文件。一个 package.json 的例子如下:
1 { "name": "some-library",2 "main": "./lib/some-library.js" }
如果 package.json 文件在 ./some-library 文件夹中,那么 require(‘./some-library‘) 将会尝试加载 ./some-library/lib/some-library.js。
这是 Node 能感知 package.json 文件的程度。
如果在文件夹中没有 package.json 文件,那么 node 将尝试加载 index.js 或 index.node 文件。例如,如果在上述例子中没有 package.json 文件,那么 require(‘./some-library‘) 将尝试加载:
- ./some-library/index.js
- ./some-library/index.node
Caching(缓存)
多次调用 require(‘foo‘) 不会引起模块代码被执行多次。这是个很重要的特性。有了它,“部分完成”的对象才能被返回,因此允许依赖能被及时加载,即使它们会引起循环引用。
如果你想一个模块会执行多次代码,那么导出一个函数,并调用那个函数。
Module Caching Caveats(模块缓存警告)
The module Object(module 对象)
在每个模块中,变量 module 是指向代表当前模块的对象的引用。为了更方便,module.exports 也可以通过模块的全局变量 exports 获得。module 不是真正的全局变量而是每个模块都有的局部变量。
module.exports
module.exports 对象被模块系统创建。有时候这是无法接受的;很多人想把他们的模块作为某些类的实例。为了这个目的需要将希望得到的对象指派给 module.exports。注意将希望得到的对象指派给 exports 将只是简单地改变局部变量 exports 的引用,这很可能不是你想要的实现的。
例如假设我们创建一个名叫 a.js 的模块
1 var EventEmitter = require(‘events‘).EventEmitter;2 3 module.exports = new EventEmitter();4 5 // Do some work, and after some time emit6 // the ‘ready‘ event from the module itself.7 setTimeout(function() {8 module.exports.emit(‘ready‘);9 }, 1000);
然后在另外一个文件我们可以
1 var a = require(‘./a‘);2 a.on(‘ready‘, function() {3 console.log(‘module a is ready‘);4 });
注意给 module.exports 赋值立即完成,不能在回调函数中完成。下面的方式不会正常工作:
x.js:
1 setTimeout(function() {2 module.exports = {a: "hello" };3 }, 0);
y.js
1 var x = require(‘./x‘);2 console.log(x.a);
exports alias(exports 别名)
为了阐述这种行为,假设 require() 是这样实现的:
1 function require(...) { 2 // ... 3 function(module, exports) { 4 // Your module code here 5 exports = some_func; // re-assigns exports, exports is no longer 6 // a shortcut, and nothing is exported. 7 module.exports = some_func; // makes your module exports 0 8 } (module, module.exports); 9 return module;10 }
作为参考,如果 exports 和module.exports 之间的关系对你来说想变魔术一样,忽略 exports 只使用 module.exports。
module.require(id)
● id 字符串
● 返回值: 对象 解析出来的模块的 module.exports
module.require 方法提供一个加载模块的途径使得就好像 require() 是在原始的模块被调用的一样。
注意为了这样做,你必须得到一个 module 对象的引用。由于 require() 返回 module.exports,且 module 通常只能在特定的模块代码里被访问,因此为了使用它必须明确地导出。
module.id
● 字符串
模块的模块标识。典型地这是绝对路径文件名。
module.filename
module.loaded
module.parent
module.children
All Together...(综述...)
综上所述,下面是 require.resolve 上层算法伪代码。
require(X) from module at path Y1. If X is a core module, a. return the core module b. STOP2. If X begins with ‘./‘ or ‘/‘ or ‘../‘ a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X)3. LOAD_NODE_MODULES(X, direname(Y))4. THROW "not found"LOAD_AS_FILE(X)1. If X is a file, load X as JavaScript text. STOP2. If X.js is a file, load X.js as JavaScript text. STOP3. If X.json is a file, parse X.json to a JavaScript Object. STOP4. If X.node is a file, load X.node as binary addon. STOPLOAD_AS_DIRECTORY(X)1. If X/package.json is a file a. Parse X/package.json, and look for "main" field. b. let M = X + (json main field) c. LOAD_AS_FILE(M)2. If X/index.js is a file, load X/index.js as JavaScript text. STOP3. If X/index.node is a file, load X/index.node as binary addon. STOPLOAD_NODE_MODULES(X, START)1. let DIRS=NODE_MODULES_PATHS(START)2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X)NODE_MODULES_PATHS(START)1. let PARTS = path split(START)2. let ROOT = index of first instance of "node_modules" in PARTS, or 03. let I = count of PARTS -14. let DIRS = []5. while I > ROOT a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. let I = I - 16. return DIRS
Loading from the global folders(从全局文件夹加载)
另外,node 会查找一下的位置:
- 1: $HOME/.node_modules
- 2: $HOME/.node_libraries
- 3: $PREFIX/lib/node
这些主要是因为历史的原因。强烈推荐你将你的依赖放置在本地 node_modules 文件夹。它们会被加载得更快,更可靠。
Accessing the main module(访问主模块)
1 require.main === module
对应文件 foo.js,如果通过 node foo.js 运行该值将是 true,但如果通过 require(‘./foo‘) 运行给支将是 false。
因为 module 提供了一个 filename 属性(通常和 __filename相等),当前应用程序的入口点可以通过检查 require.main.filename 得到。
Addenda: Package Manager Tips(附录:包管理技巧)
下面我们给出一个可行的推荐目录结构:
假设我们想在 /usr/lib/node/<some-package>/<some-version> 有一个文件夹放置一个包的特定版本的内容。
包可能依赖其它包。为了安装包 foo,你可能需要安装包 bar 的一个特定的版本。包 bar 可能自己也有依赖,在某些情况下,这些依赖设置可能冲突或形成循环。
由于 Node 查找任何模块加载时的真实路径( 也就是说,解析成符号链接),然后查找它们在 node_modules 文件夹中如上所述的依赖,这种情况对使用如下结构解析非常简单:
- /usr/lib/node/foo/1.2.3/ —— foo 包1.2.3版本的内容。
- /usr/lib/node/bar/4.3.2/ —— foo 包依赖的 bar 包内容。
- /usr/lib/node/foo/1.2.3/node_modules/bar —— 指向 /usr/lib/node/bar/4.3.2 的符号链接。
- /usr/lib/node/bar/4.3.2/node_modules/* —— bar 包依赖的包的符号链接。
当 foo 包中的代码 require(‘bar‘),它将得到符号链接指向 /usr/lib/node/foo/1.2.3/node_modules/bar 的版本。然后,当 bar 包中的代码调用 requier(‘quux‘),它将得到符号连接指向 /usr/lib/node/bar/4.3.2/node_modules/quux 的版本。
此外,为了使模块搜索过程更加理想,我们可以将包放到 /usr/lib/node_modules/<name>/<version> 而不是直接放到 /usr/lib/node。那么 node 将不会烦恼于查找 /usr/node_modules 和 /node_modules 中不存在的依赖。
为了使模块能被 node REPL 使用,把 /usr/lib/node_modules 文件夹也添加到 $NODE_PATH 环境变量也很有用。因为模块查找使用的 node_modules 文件夹都是相对,且基于调用 require() 的文件的真实路径,包本身可能在任何地方。
NODE.JS API —— Modules(模块)