Node.js模块
在计算机程序的开发过程中,随着程序
代码越写越多,在
一个文件里
代码就会越来越长,越来越不容易维护。
为了编写可维护的
代码,我们把很多
函数分组,分别放到不同的
文件里,这样,每个
文件包含的
代码就相对较少,很多编程语言都采用这种组织
代码的方式。
在Node环境中,
一个.js
文件就称之为
一个模块(module)。
使用模块有什么好处?
最大的好处是大大提高了
代码的可维护性。其次,编写
代码不必从零开始。当
一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,
包括Node内置的模块和来自第三方的模块。
使用模块还可以避免
函数名和变量名冲突。相同名字的
函数和变量
完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必
考虑名字会与其他模块冲突。
在上一节,我们编写了
一个Hello.js
文件,这个
Hello.js
文件就是
一个模块,模块的名字就是
文件名(去掉.js后缀),所以
Hello.js
文件就是名为
Hello的模块。
我们把
Hello.js改造一下,创建
一个函数,这样我们就可以在其他地方
调用这个
函数:
'use
Strict';
var s = '
Hello';
function greet(
Name) {
console.log(s + ',' + name + '!'
);
}
module.exports = greet;
函数greet()是我们在
Hello模块中定义的,你可能注意到最后一行是
一个奇怪的赋值语句,它的意思是,把
函数greet作为模块的
输出暴露出去,这样其他模块就可以使用greet
函数了。
问题是其他模块怎么使用
Hello模块的这个greet
函数呢?我们再编写@L_838_1
@main.js
文件,
调用Hello模块的greet
函数:
'use
Strict';
// 引入
Hello模块:
var greet =
require('./
Hello'
);
var s = 'Michael';
greet(s
); //
Hello,Michael!
注意到引入
Hello模块用Node提供的
require
函数:
var greet =
require('./
Hello'
);
引入的模块作为变量保存在greet变量中,那greet变量到底是什么东西?其实变量greet就是在
Hello.js中我们用module.exports = greet;
输出的greet
函数。所以,main.js就成功地引用了
Hello.js模块中定义的greet()
函数,接下来就可以直接使用它了。
在使用
require()引入模块的时候,请注意模块的相对路径。因为main.js和
Hello.js位于同
一个目录,所以我们用了当前目录.:
var greet =
require('./
Hello'
); // 不要忘了写相对目录!
如果只写模块名:
var greet =
require('
Hello'
);
则node会依次在内置模块、全局模块和当前模块下查找
Hello.js,你很可能会得到
一个错误:
module.js
throw err;
^
Error: C
Annot find module '
Hello'
at Function.Module._resolveFil
ename
at Function.Module._load
...
at Function.Module._load
at Function.Module.runMain
遇到这个
错误,你要检查:
模块名是否写对了;
模块
文件是否存在;
相对路径是否写对了。
CommonJS规范
这种模块加载机制被称为CommonJS规范。
在这个规范下,每个.js
文件都是
一个模块,它们内部各自使用的变量名和
函数名都互不冲突,例如,
Hello.js和main.js都申明了
全局变量var s = 'xxx',但互不影响。
一个模块想要
对外暴露变量(
函数也是变量),可以用module.exports = variable;,
一个模块要引用其他模块暴露的变量,用var ref =
require('module_name'
);就拿到了引用模块的变量。
结论
要在模块中对外
输出变量,用:
module.exports = variable;
输出的变量可以是任意对象、
函数、
数组等等。
要引入其他模块
输出的对象,用:
var foo =
require('other_module'
);
引入的对象具体是什么,取决于引入模块
输出的对象。
深入了解模块原理
如果你想详细地了解CommonJS的模块实现原理,请继续往下阅读。如果不想了解,请直接跳到最后做练习。
当我们编写JavaScript
代码时,我们可以申明
全局变量:
var s = 'global';
在浏览器中,大量使用
全局变量可不好。如果你在a.js中使用了
全局变量s,那么,在b.js中也使用
全局变量s,将造成冲突,b.js中对s赋值会改变a.js的运行逻辑。
也就是说,JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。
那Node.js是
如何实现这一点的?
其实要实现“模块”这个
功能,并不需要语法层面的
支持。Node.js也并不会
增加任何JavaScript语法。实现“模块”
功能的奥妙就在于JavaScript是一种
函数式编程语言,它
支持闭包。如果我们把一段JavaScript
代码用
一个函数包装起来,这段
代码的所有“全局”变量就变成了
函数内部的局部变量。
请注意我们编写的
Hello.js
代码是这样的:
var s = '
Hello';
var name = 'world';
console.log(s + ' ' + name + '!'
);
Node.js加载了
Hello.js后,它可以把
代码包装一下,变成这样执行:
(function ()
{
// 读取的
Hello.js
代码:
var s = '
Hello';
var name = 'world';
console.log(s + ' ' + name + '!'
);
//
Hello.js
代码结束
})(
);
这样一来,原来的
全局变量s现在变成了匿名
函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。
所以,Node利用JavaScript的
函数式编程的特性,轻而易举地实现了模块的隔离。
但是,模块的@L_700_32
@module.exports怎么实现?
这个
也很容易实现,Node可以先准备
一个对象module:
// 准备module对象:
var module =
{
id: '
Hello',
exports:
{}
};
var load = function (modul
E) {
// 读取的
Hello.js
代码:
function greet(
Name) {
console.log('
Hello,' + name + '!'
);
}
module.exports = greet;
//
Hello.js
代码结束
return module.exports;
};
var exported = load(modul
E);
// 保存module:
save(module,exported
);
可见,变量module是Node在加载js
文件前准备的
一个变量,并将其传入加载
函数,我们在
Hello.js中可以直接使用变量module原因就在于
它实际上是
函数的
一个参数:
module.exports = greet;
通过把参数module传递给load()
函数,
Hello.js就顺利地把
一个变量传递给了Node执行环境,Node会把module变量保存到某个地方。
由于Node保存了所有导入的module,当我们用
require()@L_444_96
@module时,Node找到对应的module,把这个module的exports变量返回,这样,另
一个模块就顺利拿到了模块的
输出:
var greet =
require('./
Hello'
);
以上是Node实现JavaScript模块的
一个简单的原理介绍。
module.exports vs exports
很多时候,你会看到,
在Node环境中,有两种
方法可以在
一个模块中
输出变量:
方法一:对module.exports赋值:
//
Hello.js
function
Hello()
{
console.log('
Hello,world!'
);
}
function greet(
Name) {
console.log('
Hello,' + name + '!'
);
}
module.exports =
{
Hello:
Hello,
greet: greet
};
方法二:直接使用exports:
//
Hello.js
function
Hello()
{
console.log('
Hello,' + name + '!'
);
}
function
Hello()
{
console.log('
Hello,world!'
);
}
export
s.Hello =
Hello;
export
s.greet = greet;
但是你不可以直接对exports赋值:
//
代码可以执行,但是模块并没有
输出任何变量:
exports =
{
Hello:
Hello,
greet: greet
};
如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:
首先,Node会
把整个待加载的
Hello.js
文件放入
一个包装
函数load中执行。在执行这个load()
函数前,Node准备好了module变量:
var module =
{
id: '
Hello',
exports:
{}
};
load()
函数最终返回module.exports:
var load = function (exports,modul
E) {
//
Hello.js的
文件内容
...
// load
函数返回:
return module.exports;
};
var exported = load(module.exports,modul
E);
也就是说,
默认情况下,Node准备的exports变量和module.exports变量实际上是同
一个变量,并且初始化为空对象
{},于是,我们可以写:
export
s.foo = function ()
{ return 'foo'; };
exports
.bar = function ()
{ return 'bar'; };
也可以写:
module.export
s.foo = function ()
{ return 'foo'; };
module.exports
.bar = function ()
{ return 'bar'; };
换句话说,Node
默认给你准备了
一个空对象
{},这样你可以直接往里面加东西。
但是,如果我们要
输出的是
一个函数或数组,那么,只能给module.exports赋值:
module.exports = function ()
{ return 'foo'; };
给exports赋值是无效的,因为赋值后,module.exports仍然是空对象
{}。
结论
如果要
输出一个键值对象
{},可以利用exports这个已存在的空对象
{},
并继续在上面
添加新的键值;
如果要
输出一个函数或数组,必须直接对module.exports对象赋值。
所以
我们可以得出结论:直接对module.exports赋值,可以应对任何情况:
module.exports =
{
foo: function ()
{ return 'foo'; }
};
或者:
module.exports = function ()
{ return 'foo'; };
@H_
674_237@最终,我们强烈建议使用module.exports = xxx的方式来
输出模块变量,这样,你只需要记忆一种
方法。
@H_
674_237@
@H_
674_237@转自:
https://www.liaoxuefeng.com/wiki/001434446
689867b27157e896e74d51a89c25cc8b43bdb3000/001434
502419592fd80bbb0613a42118ccab9435af408fd000
@H_
674_237@--- end ---