1. 程式人生 > >node.js學習筆記——模組與包

node.js學習筆記——模組與包

1.模組

1.1 模組概述

在node中,一個檔案就是一個模組,每個模組都有自己的作用域。 Node中模組分為兩類:一類是Node提供的模組,稱為核心模組;另一類是使用者編寫的模組,稱為檔案模組。 核心模組在node原始碼的編譯過程中就編譯進了二進位制執行檔案。在node程序啟動時,部分核心模組就被直接載入進記憶體中,所以這部分核心模組引入時,檔案定位和編譯執行這兩個步驟可以省略掉,並且在路徑分析中優先判斷,所以它的載入速度是最快的。 而檔案模組則是在執行時動態載入,需要完整的路徑分析、檔案定位、編譯執行過程,速度比核心模組慢。

1.2 Module

Module是nodejs提供的模組物件,該物件儲存了當前模組相關的一些資訊。

module.id是模組的識別符,通常是帶有絕對路徑的模組檔名。 module.filename是模組的檔名,絕對路徑形式。 module.loaded表示模組是否已經完成載入。 module.parent表示呼叫該模組的模組。 module.children返回一個數組,表示該模組要用到的其他模組。 module.exports表示模組對外輸出的值。

其中module.exports最特殊,表示當前模組對外輸出的介面,其他檔案載入該模組,實際上就是讀取module.exports變數。所以要注意,不能直接將exports變數指向一個值,因為這樣等於切斷了exports與module.exports的聯絡。

還有後面的module.paths,在下面會講到。

1.3 模組載入
  • 載入js檔案 require函式用於在當前模組中載入和使用別的模組,傳入一個模組名,返回一個模組匯出物件。只要傳入相對路徑或者絕對路徑的檔名就可以將對應的檔案模組匯入,如:

    var foo1 = require(’./foo’); var foo2 = require(’./foo.js’); var foo3 = require(’/home/user/foo’); var foo4 = require(’/home/user/foo.js’); // foo1至foo4中儲存的是同一個模組的匯出物件。 如圖: 在這裡插入圖片描述 一個模組中的JS程式碼僅在模組第一次被使用時執行一次,並在執行過程中初始化模組的匯出物件。之後,快取起來的匯出物件被重複利用。

  • 載入json檔案 另外,可以使用以下方式載入和使用一個JSON檔案,模組名中.json副檔名不可省略。

    var data = require(’./data.json’); 如圖: 在這裡插入圖片描述

  • require深入 require函式支援斜槓(/)或碟符(C:)開頭的絕對路徑,也支援./開頭的相對路徑。但這兩種路徑在模組之間建立了強耦合關係,一旦某個模組檔案的存放位置需要變更,使用該模組的其它模組的程式碼也需要跟著調整,變得牽一髮動全身。因此,require函式支援第三種形式的路徑,利用上面的module.paths以及特殊的node_modules目錄來查詢模組。 NodeJS定義了一個特殊的node_modules目錄用於存放模組。例如某個模組的絕對路徑是C:\Users\Administrator\Desktop\test.js,在該模組中使用require(‘dir1/test2’)方式載入模組時,則NodeJS依次嘗試使用以下路徑: C:\Users\Administrator\Desktop\node_modules\dir1\test2.js C:\Users\Administrator\node_modules\dir1\test2.js C:\Users\node_modules\dir1\test2.js C:\node_modules\dir1\test2.js 另外還可以通過NODE_PATH環境變數來查詢模組,NodeJS允許通過NODE_PATH環境變數來指定額外的模組搜尋路徑。NODE_PATH環境變數中包含一到多個目錄路徑,node會依次嘗試每個環境變數的路徑。

  • 補充 雖然一般我們使用JS編寫模組,但NodeJS也支援使用C/C++編寫二進位制模組。編譯好的二進位制模組除了副檔名是.node外,和JS模組的使用方式相同。

1.4 作用域

不同應用於瀏覽器的javascript,node的頂層模組不是window,而是global。瀏覽器javascript中全域性條件下用var宣告的變數都是window的屬性,如var str=“str”,可以通過window.str訪問到,而且值還是"str"。但是在node中就不一樣了,因為node中每個檔案都是模組,每個模組都有自己的作用域,直接在檔案中var宣告的屬性都是獨屬於這個檔案的,而不屬於任何物件。測試如下(加上module,是因為我以為str是屬於module而不是global的,然而不是):

2.包(package)

2.1 包概述

由多個子模組組成的大模組稱做包,並把所有子模組放在同一個目錄裡。在組成一個包的所有子模組中,需要有一個入口模組,入口模組的匯出物件被作為包的匯出物件。例如在C:\test目錄下有三個檔案:test1.js、test2.js、main.js,而main就是入口模組,內容如下:

exports.create = function(name){
	return {
		name: name,
		test1: require("./test1"),
		test2: require("./test2")
	}
}

在其它模組裡使用包的時候,需要載入包的入口模組:require(“C:\test\main”)就行了。

但是入口模組名稱出現在路徑裡看上去不是個好主意。而當模組的檔名是index.js,載入模組時可以使用模組所在目錄的路徑代替模組檔案路徑,所以只要將main.js改為index.js就行了。這樣require(“C:\test”)就可以匯入test包了。

如果想自定義入口模組的檔名和存放位置,就需要在包目錄下包含一個package.json檔案,並在其中指定入口模組的路徑。舉個例子:

2.2 包結構

包實際上是一個存檔檔案,即一個目錄直接打包為.zip或tar.gz格式的檔案,安裝後解壓還原為目錄。完全符合CommonJS規範的包目錄應該包含如下這些檔案:

package.json:包描述檔案 bin:用於存放可執行二進位制檔案的目錄 lib:用於存放JavaScript程式碼的目錄 doc:用於存放文件的目錄 test:用於存放單元測試用例的程式碼

2.3 包描述檔案

包描述檔案用於表達非程式碼相關的資訊,它是一個JSON格式的檔案——package.json,位於包的根目錄下,是包的重要組成部分。

package.json檔案,定義了專案所需要的各種模組,以及專案的配置資訊(比如名稱、版本、許可證等元資料)。npm install命令根據這個配置檔案,自動下載所需的模組,也就是配置專案所需的執行和開發環境。

2.4 package.json自動生成

可以通過在命令列中輸入npm init,然後一路yes,輸入自己想要的欄位來生成最基本的package.json檔案。 然後就可以得到自己想要的package.json了:

然後在package.json中新增dependencies欄位。這是使用當前包所需要依賴的包列表,這個屬性十分重要,NPM會通過這個屬性幫助自動載入依賴的包。 對應的版本可以加上各種限定,主要有以下幾種:

  • 要指定版本,如"1.2.3",要形如"大版本.次要版本.小版本"。
  • (>=/<=/>/<)+指定版本,如">=1.2.3",表示安裝大於等於1.2.3的最新版本。
  • 波浪號(tilde)+指定版本,比如"~1.2.3",表示安裝1.2.x的最新版本,且不低於1.2.3,但是不安裝1.3.x,也就是說安裝時不改變大版本號和次要版本號。
  • 插入號(caret)+指定版本,比如"ˆ1.2.3",表示安裝1.x.x的最新版本(不低於1.2.3),但是不安裝2.x.x,也就是說安裝時不改變大版本號。需要注意的是,如果大版本號為0,則插入號的行為與波浪號相同,這是因為此時處於開發階段,即使是次要版本號變動,也可能帶來程式的不相容。
  • latest:安裝最新版本。

比如添加了下列的欄位:

{
  "name": "mytest",
  "version": "0.0.1",
  "description": "a test",
  "main": "./lib/main.js",
  "directories": {
    "lib": "lib"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "liangyy75",
  "license": "ISC",
  "devDependencies": {
    "express": "4.16.3",
    "jquery": "~1.9.0",
    "pug": ">=2.0.1",
    "bootstrap": "^3.3.3",
    "mongoose": "latest"
  }
}

然後npm install,然後用npm list檢視各個包的版本,如:npm list express。

最後再說一句,其實不指定版本,直接"packageName": ""也可以下載第三方包,而且是最新的包。