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这个特殊对象下即可

注意,exportsmodule.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 copyb.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的更多相关文章

  1. 菜鸟玩云计算之二十:saltstack入门初步

    菜鸟玩云计算之二十 SaltStack 入门初步 0. saltstack 是什么 参考下面的文章: http://docs.saltstack.com/en/latest/topics/tutori ...

  2. 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 ...

  3. [nodejs]npm国内npm安装nodejs modules终极解决方案

    此方案用于设置代理和修改镜像地址都不能解决问题使用 1.npm root 确认node模块的根文件夹,全局要加-g. osx同样是此命令,先清除缓存. npm cache clean C:\Users ...

  4. [nodejs]国内npm安装nodejs modules失败的几个解决方案

    使用npm安装node模块时经常有卡住安装失败的情况,如图所示.原因在于npm服务器在美国,还有就是某强大的防火墙作用.这样的问题导致很多新手放弃使用node,几乎每天都有新手再问这个问题.现在分享一 ...

  5. 图片访问实时处理的实现(nodejs和php)

    我在访问时光网.网易云音乐等网站时,发现将它们页面中的一些图片URL修改一下就可以得到不同尺寸的图片,于是思考了其实现方案,我的思路是:URL Rewrite + 实时处理 + 缓存,对用户请求的UR ...

  6. NodeJS入门(五)—— process对象

    process对象用于处理与当前进程相关的事情,它是一个全局对象,可以在任何地方直接访问到它而无需引入额外模块. 它是 EventEmitter 的一个实例. 本章的示例可以从我的Github上下载到 ...

  7. 【nodejs笔记1】配置webstorm + node.js +express + mongodb开发博客的环境

    1. 安装webstorm 并破解 2. 安装node (以及express框架) 至官网下载并安装.(http://nodejs.org)v0.10.32   msi  安装后测试,打开命令行, c ...

  8. nodejs require//////////z

    背景 这篇文基本都是反对的,反对的很有道理,不是说我这篇文章的内容错误,因为这篇文章是我在健身房学习node的时候写的,这些知识都很粗糙,后来发现官方的稳定更详细:地址:http://nodejs.o ...

  9. 使用 AngularJS & NodeJS 实现基于token 的认证应用(转)

    认证是任何 web 应用中不可或缺的一部分.在这个教程中,我们会讨论基于 token 的认证系统以及它和传统的登录系统的不同.这篇教程的末尾,你会看到一个使用 AngularJS 和 NodeJS 构 ...

  10. 初试Nodejs——使用keystonejs创建博客网站1(安装keystonejs)

    我正在阿里云上创建一个简单的个人博客网站,刚好正在尝试NodeJs,决定找一款基于NodeJs的CMS来完成这个工作,最后找到了KeyStoneJS. KeyStoneJS是基于Express和Mon ...

随机推荐

  1. PCL—低层次视觉—点云分割(RanSaC)

    点云分割 点云分割可谓点云处理的精髓,也是三维图像相对二维图像最大优势的体现.不过多插一句,自Niloy J Mitra教授的Global contrast based salient region ...

  2. android为应用程序添加退出动画

    原本想搞一个退出程序时,把前一个应用程序的VIEW或者截图抓过来为我用,以实现更复杂的动画效果,尝试了很多方法,但都有或多或少的缺陷,可惜最后失败了.不过也算有所得.写文以标记. 其实抓图在4.0以后 ...

  3. Android 下log的使用总结

    Android 下log的使用总结 一:在源码开发模式下 1:包含头文件: #include <cutils/log.h> 2:定义宏LOG_TAG #define LOG_TAG &qu ...

  4. Android线性布局(Linear Layout)

    Android线性布局(Linear Layout) LinearLayout是一个view组(view group),其包含的所有子view都以一个方向排列,垂直或是水平方向.我们能够用androi ...

  5. [HDOJ2586]How far away?(最近公共祖先, 离线tarjan, 并查集)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586 这题以前做过…现在用tarjan搞一发…竟然比以前暴力过的慢………… 由于是离线算法,需要Que ...

  6. IntelliJ IDEA For Mac 快捷键——常用版

    一.搜索 搜索文件  command+shift+n 打开方法实现类  command+option+b 全文搜索 ctrl+shift+f (1)类和方法 查看类的继承结构 ctrl+h 查看方法的 ...

  7. 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, ...

  8. SGU 275 To xor or not to xor (高斯消元)

    题目链接 题意:有n个数,范围是[0, 10^18],n最大为100,找出若干个数使它们异或的值最大并输出这个最大值. 分析: 一道高斯消元的好题/ 我们把每个数用二进制表示,要使得最后的异或值最大, ...

  9. 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 ...

  10. Qt之进程间通信(IPC)

    简述 进程间通信,就是在不同进程之间传播或交换信息.那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区.但是,系统空间却是& ...