
1. node.js模塊概述
為了讓node.js的文件可以相互調(diào)用,node.js提供了一個簡單的模塊系統(tǒng)。模塊是node.js應(yīng)用程序基本的組成部分,文件和模塊是一一對應(yīng)的。換言之,一個node.js文件就是一個模塊,這個文件可能是javascript代碼、json或者編譯過的c/c++擴展。
其中http、fs、net等都是node.js提供的核心模塊,使用c/c++實現(xiàn),外部用javascript封裝。
2. 創(chuàng)建模塊的兩種方式
創(chuàng)建模塊有兩種方式,
- 通過exports創(chuàng)建
- 通過module.exports創(chuàng)建
2.1 通過exports創(chuàng)建模塊
node.js中,創(chuàng)建一個模塊非常簡單,我們創(chuàng)建一個main.js文件,它引用了hello模塊,代碼如下,
var hello = require('./hello')
hello.world()
在上面的代碼中,require('./hello')引入了當(dāng)前目錄下的hello.js文件。
./代表當(dāng)前目錄,node.js默認(rèn)后綴為js。
node.js提供了exports和require兩個對象,其中exports是模塊公開的接口,require用于從外部獲取一個模塊的接口,即所獲取模塊的exports對象。
接下來我們創(chuàng)建hello.js文件,如下代碼所示,
exports.world = function() {
console.log('hello world')
}
以上示例中,hello.js通過exports對象把world作為模塊的訪問接口,在main.js中通過require('./hello')加載這個模塊,然后就可以直接訪問hello.js中exports對象的成員函數(shù)了。
2.2 通過module.exports創(chuàng)建模塊
有時候我們只是想把一個對象封裝到模塊中,如下格式,
module.exports = function() {
}
以上面的格式,來寫一個模塊,如下hello.js代碼,
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName
}
this.sayHello = function() {
console.log('hello ' + name)
}
}
module.exports = Hello
這樣就可以直接獲取這個對象了,如下main.js代碼,
// main.js
var Hello = require('./hello')
hello = new Hello()
hello.setName('BYVoid')
hello.sayHello()
模塊接口的唯一變化是使用module.exports = Hello代替了exports.world = function() {}。在外部引用該模塊時,其接口對象就是要輸出的Hello對象本身,而不是原先的exports。
2.3 exports和module.exports區(qū)別
為了更好地解釋exports和module.exports之間的關(guān)系,先通過一個簡單的js示例來做一個說明,如下代碼,
var a = {name: 1}
var b = a
console.log(a)
console.log(b)
b.name = 2
console.log(a)
console.log(b)
b = {name: 3}
console.log(a)
console.log(b)
運行test.js結(jié)果為,
{ name: 1 }
{ name: 1 }
{ name: 2 }
{ name: 2 }
{ name: 2 }
{ name: 3 }
簡單解釋一下上面的代碼:a是一個對象,b是對a對象的引用,此時a和b只想同一塊內(nèi)存,所以前兩個輸出一樣;當(dāng)對b做修改時,則a和b只想同一塊內(nèi)存地址的內(nèi)容發(fā)生了改變,所以a的值改變也體現(xiàn)了出來;當(dāng)b被覆蓋時,b只想了一塊新的內(nèi)存,而a還是只想原來的內(nèi)存,所以最后兩個輸出不一樣。
明白了上面的例子之后,只需要指點3點就能了解exports和module.exports的區(qū)別了,
- module.exports初始值為一個空對象{}
- exports是只想module.exports的引用
- require()返回的是module.exports而不是exports
也就是說,module.exports才是真正的接口,exports只不過是它的一個輔助工具。最攻返回給調(diào)用者的是module.exports而不是exports。
再強調(diào)一點,在node.js中,一個文件對應(yīng)一個模塊。為了方便,模塊中會有一個exports對象,它和module.exports指向同一個變量,所以我們修改exports對象的時候也會修改module.exports對象;當(dāng)我們通過賦值方式為module.exports賦值時候,此時module.exports與exports對象指向的變量就不同了,所以無論exports對象怎么改,都和module.exports對象沒有任何關(guān)系了。
加粗!加粗!加粗!一般來說,推薦使用module.exports,盡量少使用exports。
3. require搜索module的方式
在node.js中模塊有兩種類型,即,
- 核心模塊
- 文件模塊
3.1 搜索核心模塊
核心模塊直接使用名稱獲取,例如經(jīng)常使用的http模塊,使用如下代碼獲取,
var http = require('http')
...
http.createServer()
簡要描述一下上面的代碼,node.js中自帶了一個叫做http的模塊,在上述代碼中我們請求它并把返回的值賦值給一個本地變量,這樣本地變量就編程了一個擁有所有http模塊所提供的公共方法的對象。
3.2 搜索文件模塊
在前面創(chuàng)建模塊的demo中,通過require('./hello')語法,如下代碼,
var Hello = require('./hello')
hello = new Hello()
hello.setName('BYVoid')
...
...
這里,我們使用./test來獲取自定義文件模塊,這種通過相對路徑或絕對路徑是文件模塊的搜索方式。
3.3 搜索模塊的規(guī)則
node.js加載模塊時,遵循了如下的加載規(guī)則,
- 核心模塊優(yōu)先級最高,直接使用名字加載,再有命名沖突的時候首先加載核心模塊
- 文件模塊只能按照路徑加載 -- 相對路徑或絕對路徑,并且可以省略默認(rèn)的.js后綴名
- 查找node_modules目錄,當(dāng)我們在調(diào)用npm install <name>命令的時候,會在當(dāng)前目錄下創(chuàng)建node_module目錄來安裝模塊,當(dāng)require遇到一個既不是核心模塊,又不是以路徑形式表示的模塊名稱時,會試圖在當(dāng)前目錄下的node_modules目錄中查找是不是有這樣一個模塊。如果沒有找到,則會在當(dāng)前目錄的上一層的node_modules目錄中繼續(xù)查找,反復(fù)執(zhí)行這一過程,知道遇到根目錄位置。
相對路徑 - 例如:
./hello表示同級目錄,../hello表示上層目錄絕對路徑 - 例如:
/Users/user/Desktop/js/hello