javascript是世界上最常见的部署编程语言,被用于所有的web浏览器,语言的核心,还得追溯到Netscape的时代,在与微软浏览器大战中,为了急于击败微软,这个语言就被过早的发布了,所以,不可避免的意味着它带着一些不好的特性。虽然它的开发时间很短,但javascript却附带了一些强大的特性,尽管全局共享的命名空间并不是这些脚本特性之一。
5.缓存模块:
一旦你在web页面加载了javascript的代码,就被注入到全局名称空间,这个通用的地址空间是与其他已经被加载的脚本共享的,这就会导致安全的问题、分歧和一般很难跟踪和解决的bug。值得庆幸的是node扔掉了javascript一些不好的特性,而只保留了核心的东西和实现了CommonJS模块的标准,在这个标准中,每个模块都有自己的上下文,以此来区别于其他的模块,这就意味着,模块不会污染全局作用域。
在Node中,通过文件路径或者文件名来引用模块,一个模块通过一个名字将最终映射为一个文件路径,除非这个模块是核心模块。Node的核心模块暴露给开发者一些Node的核心功能,当一个Node任务开始时,他们会被预加载。其他模块包括用NPM安装的第三方模块,或者,你和你同事已经创建的本地模块。任何类型的模块都会公开一个API借口,在开发者将他们导入到当前脚本后才够被使用,对于一个任何类型的模块,你能够用require函数,像:
var module = require(‘module_name’);
这将导入一个核心模块,或者一个通过NPM安装的模块,require函数返回一个模块所暴露的javascriptAPI,依靠这个模块,javascript对象可以是任何值。
CommonJS模块系统是你能够共享对象或文件中函数仅有的方式。对于极其复杂的应用,你应该分割成一些类,对象,或者函数在你可重用定义良好的模块,对于这些模块的使用者,你能够明确的指定一个模块暴露的是什么。在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实例。你能够另外暴露更复杂的对象,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;
这个模块暴露了两个函数和一个PI值,模块的使用者可以通过这样引用:
myModule2.printA(); // -> A
myModule2.printB(); // -> B
console.log(myModule2.pi); // -> 3.14
正如前面所解释的那样,你能用require函数去加载一个模块,在你代码中require函数调用不会改变全局命名空间的状态,因为在node中是不存在的。如果找到的模块不包含语法或者初始化错误,那么,调用require函数,就会返回一个module对象,你能够分配给任何你想要赋值的本地变量。
引用模块有几种方式,这依赖于模块的种类,分别为核心模块,通过NPM安装的第三方模块,和本地模块,下面,让我们来看看这些模块的不同加载方法。
1.加载一个核心模块:
Node有几个模块被编译到它的二进制分发包中,这些被包含的模块叫做核心模块,通过模块的名字来引用,不需要路劲信息,并且,即使与第三方模块名称相同,核心模块也会被优先加载。对于这种状况,如果你想要加载并使用http这个核心模块,你能够这样做:
var http = require(‘http’);
这个将返回http模块的对象,它的实现是在Node API文档中被描述。
2.加载一个文件模块:
你也可以加载一个非核心模块,从一个文件系统中通过提供像下边这样绝对的路径名:
var myModule = require(‘/home/person/my_modules/my_module’);
或者你能够提供一个相对当前文件的路径:
var myModule = require(‘../my_modules/my_module’);
var myModule2 = require(../lib/my_module_2’’);
注意这你能够省略.js文件的扩展名,当没有找个这个文件时,Node将会寻找.js扩展名的文件,所以,如果my_module.js存在当前目录,那么与以下这两行是等价的:
var myModule = require(‘./my_module’);
var myModule = require(‘./my_module.js’);
3.加载一个文件夹模块:
你能够用文件夹的路径加载,如:
var myModule = require(‘./myModuleDir’);
如果你这样做,Node将会搜索文件夹内部,Node将会推测这个文件夹是一个包,并且将会尝试去寻找包得定义,这个包的定义应该被命名为:
package.json,如果这个文件夹不包括包的定义文件package.json,那么这个包将会默认用index.js作为包的入口点,并且Node在这种情况下,会寻找./myModuleDir/index.js.然而,如果package.json文件在模块目录中,Node将会尝试解析这个文件,并且会寻找用main属性作为一个相对路径的入口点,对于这种状况,如果./myModuleDir/package.json文件寻找像下边这样的文件,Node将会尝试通过./myModuleDir/lib/myModule.js路径加载文件:
{
“name”: “myModule”,
“main”: “./lib/myModule.js"
}
4.从node_modules文件夹加载
如果模块的名字是不相关的,并且不是一个核心模块,Node将会尝试在当前目录下的node_modules文件夹查找,对于这种状况,如果你像如下这么做,Node将会尝试寻找./node_modules/myModule.js:
var myModule = require(‘myModule.js’);
如果node没有找到文件,那么他将会寻找它的父目录为:../node_modules/myModule.js的文件,如果还没有找到,他将继续保持像上递归的操作,直到它搜索到根模块,或者找到已经需要的模块。
你能够用这个特点去管理node_modules目录的内容,更推荐的是你能放弃它,用NPM去管理模块,这个本地的node_modules目录是NPM安装模块默认的目录,并且这个功能将NODE和NPM连在一起,做为一个程序开发者,你不会在乎这个特性,因为用NPM安装安装、更新、移除包对于你管理node_modules来说更简单些。
5.缓存模块:
模块第一次被加载后会被缓存,这意味着以后的每次调用require(‘myModule’)函数,如过模块的名称解析完全相同的文件名,则会返回完全相同的模块。
对于这种状况,可以说my_Module.js模块已经被包含在下面的模块中了,如:
console.log(‘module my_module initializing…’);
module.exports = function() {
console.log(‘Hi!’);
}
<div style="font-family: HannotateSC-W5; font-size: 13px;">console.log(‘my_module initialized.’);</div>
如果下边的脚本首次加载过上边的这段脚本:var myModuleInstance = require(‘./my_module’);
它将会打印如:
module my_module initializing…
my_module initialized
如果require两次这个模块:
var myModuleInstance1 = require(‘./my_module’);
var myModuleInstance2 = require(‘./my_module’);
你将会注意到输出仍然与上边输出相同:
module my_module initializing…
my_module initialized
这就意味着,模块初始化运行仅一次,这可能对于你知道当你正在构建一个模块初始化时,会产生一些副作用是重要的。