Node進階學習

魯鵬發表於2015-02-26

能夠完整寫出一個簡單的Node Web應用,就算是已經入門了。那麼接下來的任務呢?是否要接下來學習框架進行開發了呢,我卻不這麼認為,在前面有好多的內容不是特別的明瞭,希望這個階段可以強化前面的知識。

該階段主要任務:

  1. 強化前面的一些內容(如:模組載入機制)
  2. 進階學習一些模組(如:程式管理模組)

    (1)-模組載入

在Node基礎學習中,已經瞭解到了模組的一些知識點。如,匯出和匯入,模組載入的路徑以及模組的初始化,但是還是有些疑問。

1、模組到底如何呼叫程式載入到程式裡的?
2、為什麼只有定義在exports物件上的方法才能被外部呼叫?
3、為什麼能夠直接使用require方法,而沒有引入某個模組?

之前瞭解,Node的模組分為兩類,一類為原生(核心)模組,一類為檔案模組。但是他們的載入有什麼不一樣的嗎?下面簡要了解一下。

原生模組在Node原始碼編譯的時候編譯進了二進位制執行檔案,載入的速度最快。另一類檔案模組是動態載入的,載入速度比原生模組慢。但是Node對原生模組和檔案模組都進行了快取,於是在第二次require時,是不會有重複開銷的(這個在之前學習過)。其中原生模組都被定義在lib這個目錄下面,檔案模組則不定性。 由於通過命令列載入啟動的檔案幾乎都為檔案模組。這裡介紹一下Node如何載入檔案模組的。載入檔案模組的工作,主要由原生模組module來實現和完成,該原生模組在啟動時已經被載入,程式直接呼叫runMain靜態方法。

// bootstrap main module.
Module.runMain = function () {
// Load the main module--the command line argument.
    Module._load(process.argv[1], null, true); 
};

_load靜態方法在分析檔名之後執行var module = new Module(id, parent);,並根據檔案路徑快取當前模組物件,該模組例項物件則根據檔名載入。module.load(filename);

實際上在檔案模組中,又分為3類模組,這三類檔案模組以字尾來區分,Node會根據字尾名來決定載入方法。

  1. .js 通過fs模組同步讀取js檔案並編譯執行。
  2. .node 通過C/C++進行編寫的Addon。通過dlopen方法進行載入。
  3. .json 讀取檔案,呼叫JSON.parse解析載入。

這裡重點了解.js字尾的檔案模組的編譯過程。Node在編譯js檔案的過程中實際完 成的步驟有對js檔案內容進行頭尾包裝。以官方介紹模組的文件中app.js為例,包裝之後的app.js將會變成如下格式。

(function (exports, require, module, __filename, __dirname) {
    var circle = require('./circle.js');
    console.log('The area of a circle of radius 4 is ' + circle.area(4);
});

這段程式碼會通過vm原生模組的runInThisContext方法執行(類似eval,只是具有明確上下文,不汙染全域性),返回為一個具體的function物件。最後傳入 module物件的exportsrequire方法module檔名目錄名作為實參並執行。

這就是為什麼require並沒有定義在app.js檔案中,但是這個方法卻存在的原因。 從Node的API文件中可以看到還有__filename__dirnamemoduleexports幾個沒有定義但是卻存在的變數。其中__filename__dirname在查詢檔案路徑的過程中分析得到後傳入的。module變數是這個模組物件自身,exports是在module的建構函式中初始化的一個空物件({},而不是 null)。

在這個主檔案中,可以通過require方法去引入其餘的模組。而其實這個require方法實際呼叫的就是load方法。 load方法在載入、編譯、快取了module後,返回module的exports物件。這就是circle.js檔案中只有定義在exports物件上的方法才能被外部呼叫的原因。以上所描述的模組載入機制均定義在lib/module.js中。

本文主要內容參考取自此處

相關文章