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 ...
随机推荐
- PCL—低层次视觉—点云分割(RanSaC)
点云分割 点云分割可谓点云处理的精髓,也是三维图像相对二维图像最大优势的体现.不过多插一句,自Niloy J Mitra教授的Global contrast based salient region ...
- android为应用程序添加退出动画
原本想搞一个退出程序时,把前一个应用程序的VIEW或者截图抓过来为我用,以实现更复杂的动画效果,尝试了很多方法,但都有或多或少的缺陷,可惜最后失败了.不过也算有所得.写文以标记. 其实抓图在4.0以后 ...
- Android 下log的使用总结
Android 下log的使用总结 一:在源码开发模式下 1:包含头文件: #include <cutils/log.h> 2:定义宏LOG_TAG #define LOG_TAG &qu ...
- Android线性布局(Linear Layout)
Android线性布局(Linear Layout) LinearLayout是一个view组(view group),其包含的所有子view都以一个方向排列,垂直或是水平方向.我们能够用androi ...
- [HDOJ2586]How far away?(最近公共祖先, 离线tarjan, 并查集)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586 这题以前做过…现在用tarjan搞一发…竟然比以前暴力过的慢………… 由于是离线算法,需要Que ...
- IntelliJ IDEA For Mac 快捷键——常用版
一.搜索 搜索文件 command+shift+n 打开方法实现类 command+option+b 全文搜索 ctrl+shift+f (1)类和方法 查看类的继承结构 ctrl+h 查看方法的 ...
- 12 Useful “df” Commands to Check Disk Space in Linux
On the internet you will find plenty of tools for checking disk space utilization in Linux. However, ...
- SGU 275 To xor or not to xor (高斯消元)
题目链接 题意:有n个数,范围是[0, 10^18],n最大为100,找出若干个数使它们异或的值最大并输出这个最大值. 分析: 一道高斯消元的好题/ 我们把每个数用二进制表示,要使得最后的异或值最大, ...
- HDU 4869 Turn the pokers (2014 Multi-University Training Contest 1)
Turn the pokers Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)T ...
- Qt之进程间通信(IPC)
简述 进程间通信,就是在不同进程之间传播或交换信息.那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区.但是,系统空间却是& ...