Modules-nodejs
Modules
Node有一个简易的模块加载系统。在node中,文件和模块是一一对应的。下面示例是foo.js
加载同一目录下的circle.js
。
foo.js
的内容:
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));
circle.js
的内容:
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};
circle.js
模块输出了area()
和circumference()
两个函数。要输出某个对象,把它加到exports
这个特殊对象下即可
注意,exports
是module.exports
的一个引用,只是为了用起来方便。当你想输出的是例如构造函数这样的单个项目,那么需要使用module.exports
。
// 正确输出构造函数
module.exports = MyConstructor;
模块内的本地变量是私有的。在这里例子中,PI
这个变量就是circle.js
私有的。
模块系统的实现在require("module")
中。
循环
当存在循环的require()
调用时,一个模块可能在返回时并不会被执行。
考虑这样一种情形:
a.js
:
console.log('a starting');
exports.done = false;
var b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
b.js
console.log('b starting');
exports.done = false;
var a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
main.js
:
console.log('main starting');
var a = require('./a.js');
var b = require('./b.js');
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
会返回一个unfinished copy给b.js
。然后b.js
就会停止加载,并将其exports
对象返回给a.js
模块。
这样main.js
就把这两个模块都加载完成了。这段程序的输出如下:
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true
如果你的程序中有循环的模块依赖,请确保工作正常。
核心模块
Node中有一些模块是编译成二进制的。这些模块在本文档的其他地方有更详细的描述。
核心模块定义在node源代码的lib/
目录下。
require()
总是会优先加载核心模块。例如,require('http')
总是返回编译好的HTTP模块,而不管是否有这个名字的文件。
文件模块
如果按文件名没有查找到,那么node会添加 .js
和 .json
后缀名,再尝试加载,如果还是没有找到,最后会加上.node
的后缀名再次尝试加载。
.js
会被解析为Javascript纯文本文件,.json
会被解析为JSON格式的纯文本文件. .node
则会被解析为编译后的插件模块,由dlopen
进行加载。
模块以'/'
为前缀,则表示绝对路径。例如,require('/home/marco/foo.js')
,加载的是/home/marco/foo.js
这个文件。
模块以'./'
为前缀,则路径是相对于调用require()
的文件。 也就是说,circle.js
必须和foo.js
在同一目录下,require('./circle')
才能找到。
当没有以'/'或者'./'来指向一个文件时,这个模块要么是"核心模块",要么就是从node_modules
文件夹加载的。
如果指定的路径不存在,require()
会抛出一个code
属性为'MODULE_NOT_FOUND'
的错误。
从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
这就要求程序员应尽量把依赖放在就近的位置,以防崩溃。
Folders as Modules
可以把程序和库放到一个单独的文件夹里,并提供单一入口来指向它。有三种方法,使一个文件夹可以作为require()
的参数来加载。
首先是在文件夹的根目录创建一个叫做package.json
的文件,它需要指定一个main
模块。下面是一个package.json文件的示例。
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
示例中这个文件,如果是放在./some-library
目录下面,那么require('./some-library')
就将会去加载./some-library/lib/some-library.js
。
This is the extent of Node's awareness of package.json files.
如果目录里没有package.json这个文件,那么node就会尝试去加载这个路径下的index.js
或者index.node
。例如,若上面例子中,没有package.json,那么require('./some-library')
就将尝试加载下面的文件:
./some-library/index.js
./some-library/index.node
Caching
模块在第一次加载后会被缓存。这意味着(类似其他缓存)每次调用require('foo')
的时候都会返回同一个对象,当然,必须是每次都解析到同一个文件。
多次调用 require(foo)
未必会导致模块中的代码执行多次. 这是一个重要的功能. 借助这个功能, 可以返回部分完成的对象; 这样, 传递依赖也能被加载, 即使它们可能导致循环依赖
如果你希望一个模块多次执行,那么就输出一个函数,然后调用这个函数。
Module Caching Caveats
模块的缓存是依赖于解析后的文件名。由于随着调用的位置不同,可能解析到不同的文件(比如需从node_modules
文件夹加载的情况),所以,如果解析到其他文件时,就不能保证require('foo')
总是会返回确切的同一对象。
The module
Object
在每一个模块中,变量 module
是一个代表当前模块的对象的引用。 特别地,module.exports
可以通过全局模块对象 exports
获取到。 module
不是事实上的全局对象,而更像是每个模块内部的。
module.exports
module.exports
对象是通过模块系统产生的。有时候这是难以接受的,许多人想让他们的模块是某个类的实例。 因此,将要导出的对象赋值给 module.exports
。例如,假设我们有一个模块称之为 a.js
// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(function() {
module.exports.emit('ready');
}, 1000);
那么,在另一个文件中我们可以这样写
var a = require('./a');
a.on('ready', function() {
console.log('module a is ready');
});
注意: 给module.expors
赋值必须立即生效, 不能在回调中执行, 这样不能工作的
x.js:
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);
y.js:
var x = require('./x');
console.log(x.a);
module.require(id)
id
{String}- Return: {Object} 已解析模块的
module.exports
module.require
方法提供了一种像 require()
一样从最初的模块加载一个模块的方法。
注意,为了这样做,你必须取得一个对 module
对象的引用。 require()
返回 module.exports
,并且 module
是一个典型的只能在特定模块作用域内有效的变量,如果要使用它,就必须明确的导出。
module.id
用于区别模块的标识符。通常是完全解析后的文件名。
module.filename
模块完全解析后的文件名。
module.loaded
- {Boolean}
不论该模块是否加载完毕,或者正在加载的过程中。
module.parent
引入这个模块的模块。
module.children
这个模块引入的所有模块对象。
总体来说...
为了获取调用 require
加载的确切的文件名,使用 require.resolve()
函数。
综上所述,下面用伪代码的高级算法形式表达了 require.resolve 是如何工作的:
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let ROOT = index of first instance of "node_modules" in PARTS, or 0
3. let I = count of PARTS - 1
4. let DIRS = []
5. while I > ROOT,
a. if PARTS[I] = "node_modules" CONTINUE
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. let I = I - 1
6. return DIRS
从全局文件夹加载
如果 NODE_PATH
环境变量设置为一个以冒号分割的绝对路径的列表, 找不到模块时 node 将会从这些路径中搜索模块。 (注意:在 windows 操作系统上,NODE_PATH
是以分号间隔的)
此外,node 将会搜索以下地址:
- 1:
$HOME/.node_modules
- 2:
$HOME/.node_libraries
- 3:
$PREFIX/lib/node
$HOME
是用户的主目录,$PREFIX
是 node 里配置的 node_prefix
。
这些大多是由于历史原因。强烈建议读者将所依赖的模块放到 node_modules
文件夹里。 这样加载的更快也更可靠。
访问主模块
当 Node 直接运行一个文件时,require.main
就被设置为它的 module
。 也就是说你可以判断一个文件是否是直接被运行的
require.main === module
对于一个foo.js
文件,如果通过node foo.js
运行是true
,但是通过require('./foo')
运行却是false
。
因为module
提供了一个filename
属性(通常等于__filename
), 所以当前程序的入口点可以通过require.main.filename
来获取。
附录: 包管理技巧
Node 的 require()
函数的语义被设计的足够通用化,以支持各种常规目录结构。 包管理程序如 dpkg,rpm 和 npm 将不用修改就能够从 Node 模块构建本地包。
接下来我们将给你一个可行的目录结构建议:
假设我们希望将一个包的指定版本放在 /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 包中的代码调用require('queue')
,将会获得符号链接/usr/lib/node/bar/4.3.2/node_modules/quux
指向的版本。
此外,为了进一步优化模块搜索过程,不要将包直接放在 /usr/lib/node
目录中,而是将它们放在/usr/lib/node_modules/<name>/<version>
目录中。 这样在依赖的包找不到的情况下,就不会一直寻找/usr/node_modules
目录或 /node_modules
目录了。
为了使模块在 node 的 REPL 中可用,你可能需要将 /usr/lib/node_modules
目录加入到 $NODE_PATH
环境变量中。 由于在 node_modules 目录中搜索模块使用的是相对路径,基于调用 require()
的文件所在真实路径,因此包本身可以放在任何位置。
Modules-nodejs的更多相关文章
- 菜鸟玩云计算之二十:saltstack入门初步
菜鸟玩云计算之二十 SaltStack 入门初步 0. saltstack 是什么 参考下面的文章: http://docs.saltstack.com/en/latest/topics/tutori ...
- Visual Studio Code 断点调试Nodejs程序跳过node内部模块(internal modules)
Built-in core modules of Node.js can be referred to by the ‘magic name’ <node_internals> in a ...
- [nodejs]npm国内npm安装nodejs modules终极解决方案
此方案用于设置代理和修改镜像地址都不能解决问题使用 1.npm root 确认node模块的根文件夹,全局要加-g. osx同样是此命令,先清除缓存. npm cache clean C:\Users ...
- [nodejs]国内npm安装nodejs modules失败的几个解决方案
使用npm安装node模块时经常有卡住安装失败的情况,如图所示.原因在于npm服务器在美国,还有就是某强大的防火墙作用.这样的问题导致很多新手放弃使用node,几乎每天都有新手再问这个问题.现在分享一 ...
- 图片访问实时处理的实现(nodejs和php)
我在访问时光网.网易云音乐等网站时,发现将它们页面中的一些图片URL修改一下就可以得到不同尺寸的图片,于是思考了其实现方案,我的思路是:URL Rewrite + 实时处理 + 缓存,对用户请求的UR ...
- NodeJS入门(五)—— process对象
process对象用于处理与当前进程相关的事情,它是一个全局对象,可以在任何地方直接访问到它而无需引入额外模块. 它是 EventEmitter 的一个实例. 本章的示例可以从我的Github上下载到 ...
- 【nodejs笔记1】配置webstorm + node.js +express + mongodb开发博客的环境
1. 安装webstorm 并破解 2. 安装node (以及express框架) 至官网下载并安装.(http://nodejs.org)v0.10.32 msi 安装后测试,打开命令行, c ...
- nodejs require//////////z
背景 这篇文基本都是反对的,反对的很有道理,不是说我这篇文章的内容错误,因为这篇文章是我在健身房学习node的时候写的,这些知识都很粗糙,后来发现官方的稳定更详细:地址:http://nodejs.o ...
- 使用 AngularJS & NodeJS 实现基于token 的认证应用(转)
认证是任何 web 应用中不可或缺的一部分.在这个教程中,我们会讨论基于 token 的认证系统以及它和传统的登录系统的不同.这篇教程的末尾,你会看到一个使用 AngularJS 和 NodeJS 构 ...
- 初试Nodejs——使用keystonejs创建博客网站1(安装keystonejs)
我正在阿里云上创建一个简单的个人博客网站,刚好正在尝试NodeJs,决定找一款基于NodeJs的CMS来完成这个工作,最后找到了KeyStoneJS. KeyStoneJS是基于Express和Mon ...
随机推荐
- Java ssh 访问windows/Linux
Java ssh 访问windows/Linux 工作中遇到的问题: Java code运行在一台机器上,需要远程到linux的机器同时执行多种命令.原来采用的方法是直接调用ssh命令或者调用pli ...
- sql partition by 的使用
select a.bs_sn, a.bs_bd_no, a.bs_bk_code, a.bs_kind_no, a.bs_flag, b.det_flag, c.bp_in_no, c.bp_name ...
- BZOJ 3207 花神的嘲讽计划Ⅰ(函数式线段树)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3207 题意:给出一个数列,若干询问.每个询问查询[L,R]区间内是否存在某个长度为K的子 ...
- JAVA操作Excel 可配置,动态 生成复杂表头 复杂的中国式报表表头
转载:开源社区http://www.oschina.net/code/snippet_1424099_49530?p=2代码] [Java]代码 该代码实现了Excel复杂表头的生成 基于sql se ...
- [Android] ImageView.ScaleType设置图解
ImageView的Scaletype决定了图片在View上显示时的样子,如进行何种比例的缩放,及显示图片的整体还是部分,等等. 设置的方式包括: 1. 在layout xml中定义android ...
- pfx 转 snk
最近用 fody 加在c#工程内,但是签名只认snk ,好像是mono cecil的问题,都不认pfx,重新生成snk文件,publishkey又要变了, 底层dll引用的地方太多,要改好多cspro ...
- Nlog Layout
Nlog.config <targets> <target type="Console" name="trace" layout=&q ...
- C# 实现对接电信交费易自动缴费
他有这样一个JS PassGuardCtrl.js 部分代码 1 defaults:{ 2 obj:null, 3 random:null,/ ...
- 用户输入 i. 检测常用手势(一)
参考: http://blog.csdn.net/qq418716640/article/details/8508973http://www.cnblogs.com/mengdd/p/3335508. ...
- UVA 575 Skew Binary (水)
题意:根据这种进制的算法,例如,给你一个左式,要求推出右式.(其实右式就是一个十进制数,根据这种进位的方法来转成特殊进制的数.) 思路:观察转换特点,有点类似于二进制,但是其在后面还减一了.比如25- ...