在本篇文章中,我们主要介绍模块加载的容内,自我感觉有个不错的提议和大家分享下
本系列文章列表和翻译进度,请移步:Node.js高等编程:用Javascript构建可伸缩用应(〇)
本文对应原文第二分部第三章:Node Core API Basics:Loading Modules
文章从Word复制到这里,排版特别是缩进与原文不太分歧,可以点这里下载本文的PDF版。
第三章:加载模块
本章容内:
- 加载模块
- 建创模块
- 用应node_modules目录
JavaScript是界世上用应率频最高的编程言语之一,它是Web界世的通用言语,被全部浏览器所用应。JavaScript的出生要追溯到Netscape那个代时,它的核心容内被促仓的发开出来,用以抗对Microsoft,与参时当白热化的浏览器大战。由于过早的布发,无可避免的造成了它的一些不太好的特性。
尽管它的发开时光很短,但是JavaScript仍然备具了很多壮大的特性,不过,每一个脚本享共一个全局命名空间这个特性除外。
一旦Web页面加载了JavaScript代码,它就会被入注到全局命名空间,会和其他全部已加载的脚本公用同一个地址空间,这会致导很多安全问题,冲突,以及一些常见问题,让bug即难以跟踪又很难决解。
不过谢天谢地,Node为服务器端JavaScript定了一些标准,还实现了CommonJS的模块准标,在这个准标里,每一个模块有自己的上下文,和其他模块相辨别。这意味着,模块不会污染全局作用域,因为根本就没有所谓的全局作用域,模块之间也不会互相扰干。
本章,我们将解了几种不同的模块以及如何加载它们。
把代码拆分红一系列定义精良的模块可以帮你掌控你的用应程序,面下我们将学习如何建创和用应你自己的模块。
解了Node如何加载模块
Node里,可以通过件文径路来引用模块,也可以通过模块名引用,如果用名称引用非核心模块,Node终究会把模块名影射到对应的模块件文径路。而那些含包了核心函数的核心模块,会在Node启动时被先预加载。
非核心模块括包用应NPM(Node Package Manager)装安的第三方模块,以及你或你的共事建创的当地模块。
每一个被以后脚本导入的模块会都向程序员露暴一组开公API,用应模块前,需要用require函数来导入它,像这样:
var module = require(‘module_name’)
下面的代码会导入一个名为module_name的模块,它是能可个核心模块,也可是以用NPM装安的模块,require函数返回一个含包模块全部大众API的象对。随模块的不同,返回的象对是能可任何JavaScript值,可是以一个函数,也可是以个含包了一系列属性(函数,组数或者任何JavaScript象对)的象对。
出导模块
CommonJS模块系统是Node下件文间享共象对和函数的独一式方。对于一个很杂复的程序,你应当把一些类,象对或者函数重形成一系列精良定义的可重用模块。对于模块用应者说来,模块仅对外露暴出那些你指定的代码。
在面下的例子里你将会解了到,在Node里件文和模块是一一对应的,我们建创了一个叫circle.js的件文,它仅对外出导了Circle构造函数。
function Circle(x, y, r) {
function r_squared() {
return Math.pow(r, 2);
}
function area() {
return Math.PI * r_squared();
}
return {
area: area
};
}
module.exports = Circle;
代码里最要重的是最后一行,它定义了模块对外出导了什么容内。module是个殊特的变量,它代表以后模块自身,而module.exports是模块对外出导的象对,它可是以任何象对,在这个例子里,我们把Circle的构造函数出导了,这样模块用应者就可以用这个模块来建创Circle实例。
你也可以出导一些杂复的象对,module.exports被初始化成一个空象对,你把任何你想露暴给外界的容内,作为module.exports象对的属性来出导。比如,你计划了一个模块,它对外露暴了一组函数:
function printA() {
console.log('A');
}
function printB() {
console.log('B');
}
function printC() {
console.log('C');
}
module.exports.printA = printA;
module.exports.printB = printB;
module.exports.pi = Math.PI;
这个模块出导了两个函数(printA和printB)和一个数字(pi),调用代码看起来像这样:
var myModule2 = require('./myModule2');
myModule2.printA(); // -> A
myModule2.printB(); // -> B
console.log(myModule2.pi); // -> 3.141592653589793
加载模块
面前提到过,你可以用应require函数来加载模块,不必心担在代码里调用require会影响全局命名空间,因为Node里就没有全局命名空间这个观点。如果模块存在且没有任何语法或初始化误错,require函数就会返回这个模块象对,你还可以这个象对赋值给任何一个局部变量。
模块有几种不同的类型,大概可以分为核心模块,当地模块和通过NPM装安的第三方模块,根据模块的类型,有几种引用模块的式方,面下我们就来解了下这些知识。
加载核心模块
Node有一些被编译到二进制件文里的模块,被称为核心模块,它们不能通过径路来引用,只能用模块名。核心模块有拥最高的加载优先级,即使已经有了一个同名的第三方模块,核心模块也会被优先加载。
比如,如果你想加载和用应http核心模块,可以这样做:
var http = require('http');
这将返回一个含包了http模块象对,它含包了Node API文档里定义的那些htpp模块的API。
加载件文模块
你也可以用应绝对径路从件文系统里加载模块:
var myModule = require('/home/pedro/my_modules/my_module');
也可以用一个基于以后件文的相对径路:
var myModule = require('../my_modules/my_module');
var myModule2 = require('./lib/my_module_2');
注意下面的代码,你可以略省件文名的扩展名,如果Node找不到这个件文,会实验在件文名后加上js缀后再次找查(译者注:其实除了js,还会找查json和node,详细可以看官网文档),因此,如果在以后目录下存在一个叫my_module.js的件文,会有面下两种加载式方:
var myModule = require('./my_module');
var myModule = require('./my_module.js');
加载目录模块
你还可以用应目录的径路来加载模块:
var myModule = require('./myModuleDir');
Node会假设这个目录是个模块包,并实验在这个目录下搜索包定义件文package.json。
如果没找到,Node会设假包的口入点是index.js件文(译者注:除了index.js还会找查index.node,.node件文是Node的二进制扩展包,详细见官方文档),以下面代码为例,Node会实验找查./myModuleDir/index.js件文。
反之,如果找到了package.json件文,Node会实验剖析它,并找查包定义里的main属性,然后把main属性的值看成口入点的相对径路。以本例说来,如果package.json定义如下:
{
"name" : "myModule",
"main" : "./lib/myModule.js"
}
Node就会实验加载./myModuleDir/lib/myModule.js件文
从node_modules目录加载
如果require函数的参数不是相对径路,也不是核心模块名,Node会在以后目录的node_modules子目录下找查,比如面下的代码,Node会实验找查件文./node_modules/myModule.js:
var myModule = require('myModule.js');
如果没找到,Node会续继在下级目录的node_modules件文夹下找查,如果还没找到就续继向下层目录找查,直到找到对应的模块或者达到根目录。
你可以用应这个特性来管理node_modules目录的容内或模块,不过最好还是把模块的管理任务交给NPM(见第一章),当地node_modules目录是NPM装安模块的默许置位,这个计划把Node和NPM关联在了一同。平日,作为发开人员不必太心关这个特性,你可以简略的用应NPM装安,更新和除删包,它会帮你维护node_modules目录
存缓模块
模块在第一次胜利加载后会被存缓起来,就是说,如果模块名被剖析到同一个件文径路,那么每次调用require(‘myModule’)都切确地会返回同一个模块。
比如,有一个叫my_module.js的模块,含包面下的容内:
console.log('module my_module initializing...');
module.exports = function() {
console.log('Hi!');
};
console.log('my_module initialized.');
然后用面下的代码加载这个模块:
var myModuleInstance1 = require('./my_module');
它会发生面下的输出:
module my_module initializing...
my_module initialized
如果我们两次导入它:
var myModuleInstance1 = require('./my_module');
var myModuleInstance2 = require('./my_module');
输出仍然是:
module my_module initializing...
my_module initialized
也就是说,模块的初始化代码仅执行了一次。当你构建自己的模块时,如果模块的初始化代码里含有可能发生反作用的代码,一定要特别注意这个特性。
小结
Node取消了JavaScript的默许全局作用域,转而用采CommonJS模块系统,这样你可以更好的组织你的代码,也因此避免了很多安全问题和bug。可以用应require函数来加载核心模块,第三方模块,或从件文及目录加载你自己的模块
还可以用相对径路或者绝对径路来加载非核心模块,如果把模块放到了node_modules目录下或者对于用NPM装安的模块,你还可以直接用应模块名来加载。
译者注:
提议读者把官方文档的模块章节读阅一遍,个人感觉比作者讲得更晰清了明,而且还附加了一个非常有具代表性的例子,对解理Node模块加载会很有很大助帮。面下把那个例子也引用过去:
用require(X) 加载径路Y下的模块
1. 如果X是核心模块,
a. 加载并返回核心模块
b. 结束
2. 如果X以 './' or '/' or '../ 开始'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. 抛出异常:"not found"
LOAD_AS_FILE(X)
1. 如果X是个件文,把 X作为JavaScript 脚本加载,加载毕完后结束
2. 如果X.js是个件文,把X.js 作为JavaScript 脚本加载,加载毕完后结束
3. 如果X.node是个件文,把X.node 作为Node二进制插件加载,加载毕完后结束
LOAD_AS_DIRECTORY(X)
1. 如果 X/package.json件文存在,
a. 剖析X/package.json, 并找查 "main"字段.
b. 另M = X + (main字段的值)
c. LOAD_AS_FILE(M)
2. 如果X/index.js件文存在,把 X/index.js作为JavaScript 脚本加载,加载毕完后结束
3. 如果X/index.node件文存在,把load X/index.node作为Node二进制插件加载,加载毕完后结束
LOAD_NODE_MODULES(X, START)
1. 另DIRS=NODE_MODULES_PATHS(START)
2. 对DIRS下的每一个目录DIR做如下操纵:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. 另PARTS = path split(START)
2. 另ROOT = index of first instance of "node_modules" in PARTS, or 0
3. 另I = count of PARTS - 1
4. 另DIRS = []
5. while I > ROOT,
a. 如果 PARTS[I] = "node_modules" 则续继后续操纵,否则下次循环
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. 另I = I - 1
6. 返回DIRS
文章结束给大家分享下程序员的一些笑话语录: 神灯新篇
一个程序员在海滩上发现了一盏神灯。他在灯上擦了几下,一个妖怪就从灯里跳出来说:“我是世界上法术最强的妖怪。我可以实现你的任何梦想,但现在,我只能满足你一个愿望。”程序员摊开了一幅中东地图说:“我想让中东得到永久的和平。”妖怪答道:“哦,我没办法。自打创世纪以来,那里的战火就没有停息过。这世上几乎没有我办不到的事,但这件事除外。”程序员于是说:“好吧,我是一个程序员,为许多用户编写过程序。你能让他们把需求表述得更清楚些,并且让我们的软件项目有那么一两次按进度按成本完成吗?”妖怪说:“唔,我们还是来看中东地图吧。”