Node.js進階編程:用Javascript構建可伸縮應用(3)2.3 核心API基礎-載入模組

來源:互聯網
上載者:User
文章目錄
  • 瞭解Node如何載入模組
  • 匯出模組
  • 載入模組
  • 從node_modules目錄載入
  • 緩衝模組

本系列文章列表和翻譯進度,請移步:Node.js進階編程:用Javascript構建可伸縮應用(〇)

本文對應原文第二部分第三章:Node Core API Basics:Loading Modules

文章從Word複製到這裡,排版特別是縮排與原文不太一致,可以點這裡下載本文的PDF版。

第三章:載入模組本章內容:
  • 載入模組
  • 建立模組
  • 使用node_modules目錄

 

JavaScript是世界上使用頻率最高的程式設計語言之一,它是Web世界的通用語言,被所有瀏覽器所使用。JavaScript的誕生要追溯到Netscape那個時代,它的核心內容被倉促的開發出來,用以對抗Microsoft,參與當時白熱化的瀏覽器大戰。由於過早的發布,無可避免的造成了它的一些不太好的特性。

儘管它的開發時間很短,但是JavaScript依然具備了很多強大的特性,不過,每個指令碼共用一個全域命名空間這個特性除外。

一旦Web頁面載入了JavaScript代碼,它就會被注入到全域命名空間,會和其他所有已載入的指令碼公用同一個地址空間,這會導致很多安全問題,衝突,以及一些常見問題,讓bug即難以跟蹤又很難解決。

不過謝天謝地,Node為伺服器端JavaScript定了一些規範,還實現了CommonJS的模組標準,在這個標準裡,每個模組有自己的上下文,和其他模組相區分。這意味著,模組不會汙染全域範圍,因為根本就沒有所謂的全域範圍,模組之間也不會相互幹擾。

本章,我們將瞭解幾種不同的模組以及如何載入它們。

把代碼拆分成一系列定義良好的模組可以幫你掌控你的應用程式,下面我們將學習如何建立和使用你自己的模組。

瞭解Node如何載入模組

Node裡,可以通過檔案路徑來引用模組,也可以通過模組名引用,如果用名稱引用非核心模組,Node最終會把模組名影射到對應的模組檔案路徑。而那些包含了核心函數的核心模組,會在Node啟動時被積極式載入。

非核心模組包括使用NPM(Node Package Manager)安裝的第三方模組,以及你或你的同事建立的本地模組。

每個被當前指令碼匯入的模組都會向程式員暴露一組公開API,使用模組前,需要用require函數來匯入它,像這樣:

var module = require(‘module_name’)

上面的代碼會匯入一個名為module_name的模組,它可能是個核心模組,也可以是用NPM安裝的模組,require函數返回一個包含模組所有公用API的對象。隨模組的不同,返回的對象可能是任何JavaScript值,可以是一個函數,也可以是個包含了一系列屬性(函數,數組或者任何JavaScript對象)的對象。

匯出模組

CommonJS模組系統是Node下檔案間共用對象和函數的唯一方式。對於一個很複雜的程式,你應該把一些類,對象或者函數重構成一系列良好定義的可重用模組。對於模組使用者來說,模組僅對外暴露出那些你指定的代碼。

在下面的例子裡你將會瞭解到,在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的建構函式匯出了,這樣模組使用者就可以用這個模組來建立Circle執行個體。

你也可以匯出一些複雜的對象,module.exports被初始化成一個Null 物件,你把任何你想暴露給外界的內容,作為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;

這個模組匯出了兩個函數(printA和printB)和一個數字(pi),調用代碼看起來像這樣:

       var myModule2 = require('./myModule2');

                   myModule2.printA(); // -> A

                   myModule2.printB(); // -> B

                   console.log(myModule2.pi); // -> 3.141592653589793

載入模組

前面提到過,你可以使用require函數來載入模組,不用擔心在代碼裡調用require會影響全域命名空間,因為Node裡就沒有全域命名空間這個概念。如果模組存在且沒有任何文法或初始化錯誤,require函數就會返回這個模組對象,你還可以這個對象賦值給任何一個局部變數。

模組有幾種不同的類型,大概可以分為核心模組,本地模組和通過NPM安裝的第三方模組,根據模組的類型,有幾種引用模組的方式,下面我們就來瞭解下這些知識。

載入核心模組

Node有一些被編譯到二進位檔案裡的模組,被稱為核心模組,它們不能通過路徑來引用,只能用模組名。核心模組擁有最高的載入優先順序,即使已經有了一個同名的第三方模組,核心模組也會被優先載入。

比如,如果你想載入和使用http核心模組,可以這樣做:

         var http = require('http');

這將返回一個包含了http模組對象,它包含了Node API文檔裡定義的那些htpp模組的API。

負載檔案模組

你也可以使用絕對路徑從檔案系統裡載入模組:

var myModule = require('/home/pedro/my_modules/my_module');

也可以用一個基於當前檔案的相對路徑:

                   var myModule = require('../my_modules/my_module');

                   var myModule2 = require('./lib/my_module_2');

注意上面的代碼,你可以省略檔案名稱的副檔名,如果Node找不到這個檔案,會嘗試在檔案名稱後加上js尾碼再次尋找(譯者註:其實除了js,還會尋找json和node,具體可以看官網文檔),因此,如果在目前的目錄下存在一個叫my_module.js的檔案,會有下面兩種載入方式:

                   var myModule = require('./my_module');

                   var myModule = require('./my_module.js');

載入目錄模組

         你還可以使用目錄的路徑來載入模組:

var myModule = require('./myModuleDir');

         Node會假定這個目錄是個模組包,並嘗試在這個目錄下搜尋套件定義檔package.json。

如果沒找到,Node會假設包的進入點是index.js檔案(譯者註:除了index.js還會尋找index.node,.node檔案是Node的二進位擴充包,具體見官方文檔),以上面代碼為例,Node會嘗試尋找./myModuleDir/index.js檔案。

反之,如果找到了package.json檔案,Node會嘗試解析它,並尋找包定義裡的main屬性,然後把main屬性的值當作進入點的相對路徑。以本例來說,如果package.json定義如下:

                   {

                            "name" : "myModule",

                            "main" : "./lib/myModule.js"

                   }

Node就會嘗試載入./myModuleDir/lib/myModule.js檔案

從node_modules目錄載入

如果require函數的參數不是相對路徑,也不是核心模組名,Node會在目前的目錄的node_modules子目錄下尋找,比如下面的代碼,Node會嘗試尋找檔案./node_modules/myModule.js:

var myModule = require('myModule.js');

如果沒找到,Node會繼續在上級目錄的node_modules檔案夾下尋找,如果還沒找到就繼續向上層目錄尋找,直到找到對應的模組或者到達根目錄。

你可以使用這個特性來管理node_modules目錄的內容或模組,不過最好還是把模組的管理工作交給NPM(見第一章),本地node_modules目錄是NPM安裝模組的預設位置,這個設計把Node和NPM關聯在了一起。通常,作為開發人員不必太關心這個特性,你可以簡單的使用NPM安裝,更新和刪除包,它會幫你維護node_modules目錄

緩衝模組

模組在第一次成功載入後會被緩衝起來,就是說,如果模組名被解析到同一個檔案路徑,那麼每次調用require(‘myModule’)都確切地會返回同一個模組。

比如,有一個叫my_module.js的模組,包含下面的內容:

console.log('module my_module initializing...');

module.exports = function() {

         console.log('Hi!');

};

console.log('my_module initialized.');

然後用下面的代碼載入這個模組:

var myModuleInstance1 = require('./my_module');

它會產生下面的輸出:

module my_module initializing...

my_module initialized

如果我們兩次匯入它:

var myModuleInstance1 = require('./my_module');

var myModuleInstance2 = require('./my_module');

輸出依然是:

module my_module initializing...

my_module initialized

也就是說,模組的初始化代碼僅執行了一次。當你構建自己的模組時,如果模組的初始化代碼裡含有可能產生副作用的代碼,一定要特別注意這個特性。

小結

Node取消了JavaScript的預設全域範圍,轉而採用CommonJS模組系統,這樣你可以更好的組織你的代碼,也因此避免了很多安全問題和bug。可以使用require函數來載入核心模組,第三方模組,或從檔案及目錄載入你自己的模組

還可以用相對路徑或者絕對路徑來載入非核心模組,如果把模組放到了node_modules目錄下或者對於用NPM安裝的模組,你還可以直接使用模組名來載入。

 

譯者註:

建議讀者把官方文檔的模組章節閱讀一遍,個人感覺比作者講得更清晰明了,而且還附加了一個非常具有代表性的例子,對理解Node模組載入會很有很大協助。下面把那個例子也引用過來:

用require(X) 載入路徑Y下的模組

1. 如果X是核心模組,

   a. 載入並返回核心模組

   b. 結束

2. 如果X以 './' or '/' or '../ 開始'

   a. LOAD_AS_FILE(Y + X)

   b. LOAD_AS_DIRECTORY(Y + X)

3. LOAD_NODE_MODULES(X, dirname(Y))

4. 拋出異常:"not found"

 

LOAD_AS_FILE(X)

1. 如果X是個檔案,把 X作為JavaScript 指令碼載入,載入完畢後結束

2. 如果X.js是個檔案,把X.js 作為JavaScript 指令碼載入,載入完畢後結束

3. 如果X.node是個檔案,把X.node 作為Node二進位外掛程式載入,載入完畢後結束

 

LOAD_AS_DIRECTORY(X)

1. 如果 X/package.json檔案存在,

   a. 解析X/package.json, 並尋找 "main"欄位.

   b. 另M = X + (main欄位的值)

   c. LOAD_AS_FILE(M)

2. 如果X/index.js檔案存在,把 X/index.js作為JavaScript 指令碼載入,載入完畢後結束

3. 如果X/index.node檔案存在,把load X/index.node作為Node二進位外掛程式載入,載入完畢後結束

 

LOAD_NODE_MODULES(X, START)

1. 另DIRS=NODE_MODULES_PATHS(START)

2. 對DIRS下的每個目錄DIR做如下操作:

   a. LOAD_AS_FILE(DIR/X)

   b. LOAD_AS_DIRECTORY(DIR/X)

 

NODE_MODULES_PATHS(START)

1. 另PARTS = path split(START)

2. 另ROOT = index of first instance of "node_modules" in PARTS, or 0

3. 另I = count of PARTS - 1

4. 另DIRS = []

5. while I > ROOT,

   a. 如果 PARTS[I] = "node_modules" 則繼續後續操作,否則下次迴圈

   c. DIR = path join(PARTS[0 .. I] + "node_modules")

   b. DIRS = DIRS + DIR

   c. 另I = I - 1

6. 返回DIRS

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.