標籤:擷取 csdn tty boolean 動態載入 講解 expr 工作 關閉
前言:工欲善其事,必先利其器。模組系統是nodejs組織管理代碼的利器也是調用第三方代碼的途徑,本文將詳細講解nodejs的模組系統。在文章最後執行個體分析一下exprots和module.exprots。
nodejs的模組什麼是模組?
node.js通過實現CommonJS的Modules/1.0標準引入了模組(module)概念,模組是Node.js的基本組成部分.一個node.js檔案就是一個模組,也就是說檔案和模組是一一對應的關係.這個檔案可以是JavaScript代碼,JSON或者編譯過的C/C++擴充.
Node.js的模組分為兩類,一類為原生(核心)模組,一類為檔案模組。
在檔案模組中,又分為3類別模組。這三類檔案模組以尾碼來區分,Node.js會根據尾碼名來決定載入方法。
- .js。通過fs模組同步讀取js檔案並編譯執行。
- .node。通過C/C++進行編寫的Addon。通過dlopen方法進行載入。
- .json。讀取json檔案,調用JSON.parse解析載入。
Node提供了exports和require兩個對象,其中exports是模組公開的介面,require用於從外部擷取一個模組介面,即所擷取模組的exports對象
require和exports
require
require函數用於在當前模組中載入和使用別的模組,傳入一個模組名,返回一個模組匯出對象。require方法接受以下幾種參數的傳遞:
- http、fs、path等。原生模組。
- ./mod或../mod。相對路徑的檔案模組。
- /a/mod,絕對路徑的檔案模組。
- mod,非原生模組的檔案模組。
exports
exports對象是當前模組的匯出對象,用於匯出模組公有方法和屬性。別的模組通過require函數使用當前模組時得到的就是當前模組的exports對象。
module
通過module對象可以訪問到當前模組的一些相關資訊,但最多的用途是替換當前模組的匯出對象
id {String}
Return: {Object} 已解析模組的 module.exports
這個方法提供了一種像 require() 一樣從最初的模組載入一個模組的方法。
module.id:{String}類型,用於區別模組的標識符。通常是完全解析後的檔案名稱
module.filename:{String}類型,模組完全解析後的檔案名稱。
module.loaded:{Boolean}類型,判斷該模組是否載入完畢。
module.parent:{Module Object}類型,返回引入了本模組的其他模組。
module.children:{Array}類型,該模組所引入的其他子模組。
demo1 module.exports的使用
sayHello.js:
function sayHello() { console.log(‘hello‘);}module.exports = sayHello;
app.js:
var sayHello = require(‘./sayHello‘);sayHello();//hello
代碼講解:
定義一個sayHello模組,模組裡定義了一個sayHello方法,通過替換當前模組exports對象的方式將sayHello方法匯出。
在app.js中載入這個模組,得到的是一個函數,調用該函數,控制台列印hello。
demo2 匿名替換
sayWorld.js
module.exports = function () { console.log(‘world‘);}
app.js
var sayWorld = require(‘./sayWorld‘);sayWorld();//world
代碼講解
與上面稍有不同,這次是匿名替換。
demo3 替換為字串
不僅可以替換為方法,也可以替換為字串等。
stringMsg.js
module.exports = ‘i am a string msg!‘;
app.js
var string = require(‘./stringMsg‘);console.log(string);//i am a string msg!
demo4 exports匯出多個變數
當要匯出多個變數怎麼辦呢?這個時候替換當前模組對象的方法就不實用了,我們需要用到exports對象。
useExports.js
exports.a = function () { console.log(‘a exports‘);}exports.b = function () { console.log(‘b exports‘);}
app,js
var useExports = require(‘./useExports‘);useExports.a();useExports.b();//a exports//b exports
當然,將useExports.js改成這樣也是可以的:
module.exports.a = function () { console.log(‘a exports‘);}module.exports.b = function () { console.log(‘b exports‘);}
下面通過gif圖進行示範:
module.exports和exports在文章的最後會進行詳細講解。
模組初始化
一個模組中的JS代碼僅在模組第一次被使用時執行一次,並在執行過程中初始化模組的匯出對象。之後,緩衝起來的匯出對象被重複利用。
舉個例子,count,js:
var i = 0;function count() { return ++i;}exports.count = count;
app.js
var c1 = require(‘./count‘);var c2 = require(‘./count‘);console.log(c1.count());console.log(c2.count());console.log(co2.count());//1//2//3
可以看到,count.js並沒有因為被require了兩次而初始化兩次。
主模組
通過命令列參數傳遞給NodeJS以啟動程式的模組被稱為主模組。主模組負責調度組成整個程式的其它模組完成工作。例如通過以下命令啟動程式時,我們剛剛一直使用的app.js就是主模組。
二進位模組
雖然一般我們使用JS編寫模組,但NodeJS也支援使用C/C++編寫二進位模組。編譯好的二進位模組除了副檔名是.node外,和JS模組的使用方式相同。雖然二進位模組能使用作業系統提供的所有功能,擁有無限的潛能,但對於不熟悉C/C++的人而言編寫過於困難,並且難以跨平台使用,因此本文不作講解。
模組的載入優先順序
由於Node.js中存在4類別模組(原生模組和3種檔案模組),儘管require方法極其簡單,但是內部的載入卻是十分複雜的,其載入優先順序也各自不同,下面是require載入的邏輯圖:
原生模組在Node.js原始碼編譯的時候編譯進了二進位執行檔案,載入的速度最快。另一類檔案模組是動態載入的,載入速度比原生模組慢。但是Node.js對原生模組和檔案模組都進行了緩衝,於是在第二次require時,是不會有重複開銷的。
exports與module.exports
這裡可能是最容易混淆的地方了。
我們先來看一個例子:
modOne.js
exports.hello = function () { console.log("hello");}module.exports = function () { console.log(‘world‘);}
app.js
var one = require(‘./modOne‘);//one.hello(); //執行這句話會報錯one.hello is not a functionone() //列印world
這是為什麼呢?我們得先從exports 與module.exports 說起。
其實,exports 是module.exports的一個引用,exports 的地址指向module.exports。
而我們的modOne.js中通過module.exports = function的方式將module.exports給替換掉了。
而require方法所返回的是module.exports這個實實在在的對象,但是它已經被替換成了function,這就導致了exports指向了空,所以,你所定義的exports.hello是無效的。
用一個通俗易懂的例子來重新解釋一遍。
比如你在電腦的D盤下建立了一個exports文字文件,然後你右鍵->發送到案頭捷徑。
D盤就相當於nodejs中的module,這個exports文字文件就相當於nodejs中模組的exports對象,捷徑就相當於nodejs中指向exports對象引用
D:/exportes.txt ==> module.exportes
exportes.txt捷徑 ==> exportes
然後,你看exportes.txt不爽,把它給刪了,然後建立了一個word文檔–exports.docx。
這個時候你案頭上的捷徑就沒用了,雖然也叫exports,但是你是訪問不到這個新的word檔案的。
對於nodejs也一樣,當你把module.exportes對象覆蓋了,換成了其他東西的時候,exportes這個引用就失效了。
同樣,我們還可以用這個例子來理解為什麼exportes也可以用來匯出模組。
我們是這樣使用exportes的:
exports.hello = function () { console.log("hello");}
這段代碼其實等同於:
module.exports.hello = function () { console.log("hello");}
怎麼理解呢。還是剛才的txt檔案,這次沒有刪除。
D:/exportes.txt ==> module.exportes
exportes.txt捷徑 ==> exportes
你在案頭開啟了exportes.txt捷徑,然後在裡面輸入hello,然後儲存,關閉。
你再開啟D:/exportes.txt,你會發現你可以看到剛剛寫的hello,你又在後面添加了一句“world”,儲存關閉。
返回案頭,開啟捷徑,你會看到helloworld。
所以說你使用’exports.屬性’和’module.exportes.屬性’是等同的。
這也就能很好的解釋下面這個問題了:
exports = function() { console.log(‘hello‘);}//這樣寫會報錯
這樣相當於把exprots這個引用覆蓋掉了,你把txt檔案的捷徑改成docx的捷徑還能開啟原來的txt檔案嗎?顯然是不能的。
最後做一個總結:
當我們想讓模組匯出的是一個對象時, 使用exports 和 module.exports 都可以(但 exports 也不能重新覆蓋為一個新的對象),而當我們想匯出非對象介面時,就必須也只能覆蓋 module.exports 。
nodejs的模組系統(執行個體分析exprots和module.exprots)