Node.js 系列 - 模塊機制

做爲還在漫漫前端學習路上的一位自學者。我以學習分享的方式來整理本身對於知識的理解,同時也但願可以給你們做爲一份參考。但願可以和你們共同進步,若有任何紕漏的話,但願你們多多指正。感謝萬分!html


什麼是模塊?

以編程角度來講, "模塊" 指的是可以提供必定功能或數據的程序語句集合. 模塊具有和外部聯繫的接口 (其餘模塊或程序調用該模塊的方式)前端

在 Node.js 中, 每一個文件就被視爲一個模塊. 這個文件多是 JavaScript 編寫的文件、JSON 或者用 C/C++ 編譯的二進制文件. 經過對外接口來向外部暴露功能或者數據, 模塊之間能夠互相調用.node

爲何要用模塊?

隨着開發複雜度的提高, 將代碼都寫在一處的傳統開發方式, 顯現出了不少問題:npm

  • 很容易出現代碼重複. 開發人員很容易將一個功能的代碼重複地寫了好多遍. 這致使了若是往後功能需求出現了變動, 就要有多處代碼須要被更改. 隨着應用規模的增大, 代碼會變得難以維護.
  • 難以確保代碼質量. 全部代碼都混在一塊兒, 實現不一樣功能的代碼全都被寫在一個文件中, 使得對於單個功能的獨立測試變得困難.
  • 難以查錯. 全部代碼都混在一塊兒, 程序運行出現 BUG 了, 很難快速定位.
  • 性能浪費. 由於代碼都寫在一個文件中, 在只調用文件中一段代碼的時候, 也會致使整個文件都加載一遍. 這會使不少根本用不到的代碼對性能形成浪費.
  • 難以多人寫協做. 全部代碼都放在一個文件中, 使得多人協做變得困難. 開發人員難以確認其餘人作了什麼修改, 添加了什麼東西. 很容易一我的出了錯誤, 致使整個程序崩潰.
  • 等等...

模塊化開發

經過使用模塊機制, 咱們能夠把一個複雜程序的各個功能拆分, 分別封裝到不一樣的模塊. 每一個模塊職責單一 (各管一件事, 之間沒交集) 經過開發新模塊, 和對已有模塊的複用來實現各類功能. 這種開發方式被稱爲 "模塊化開發".編程

應用模塊化開發, 使得各個功能都封裝在獨立的文件中, 分而治之, 互不干擾. 使得代碼易於維護和複用. 同時每一個模塊中的變量也不會污染全局做用域, 避免了命名衝突.json

CommonJS

Node.js 參照 CommonJS 標準實現了模塊機制. CommonJS 是一套代碼規範, 目的是爲了構建 JavaScript 在瀏覽器以外的生態系統 (服務器端, 桌面端). JavaScript 誕生之初只是爲了寫網頁小腳本, 並不做爲開發大型複雜應用的語言, 其自身有不少不足. 而且, 官方規範 (ECMAScript) 制定的時間較早, 涵蓋範圍較小, 對於後端開發而言, 例如文件系統, I/O 流, 模塊系統, 等等方面都沒有相應的標準. 基於種種的不足, CommonJS 規範致力於彌補 JavaScript 沒有標準的缺陷, 讓 JavaScript 有能力去開發複雜應用, 同時具有跨平臺能力.後端

下面是一個 Node.js 的模塊使用示例:瀏覽器

在代碼中, 開頭經過 require 方法引入了 Node.js 自帶的 http 模塊. 並用此模塊實現了一個 HTTP 服務器.服務器

const http = require('http');

function myNodeServer(req, res){
res.writeHead(200, {'Content-type':'text/plain'});
res.write('Hello World'); 
res.end();
}

http.createServer(myNodeServer).listen(3000); //監聽 3000 端口

console.log('Server is running!'); 
複製代碼

模塊分類

前文說, 在 Node.js 中, 每一個文件就被視爲一個模塊. 這個文件多是 JavaScript 編寫的文件、JSON 或者用 C/C++ 編譯的二進制文件.網絡

模塊能夠分紅三類:

Untitled Diagram(6)

  • 核心模塊 』: Node.js 自帶的原生模塊. 好比, http, fs, url. 其分爲 C/C++ 編寫的和 JavaScript 編寫的兩部分. C/C++ 模塊存放在 Node.js 源代碼目錄的 src/ 目錄下. JavaScript 模塊存放在 lib/ 目錄下. 
  • 文件模塊 』: 開發人員在本地寫的模塊. 加載時經過相對路徑, 絕對路徑來定位模塊所在位置.
  • 第三方模塊 』: 別人編寫的模塊, 經過包管理工具, 好比 npm, yarn, 能夠將其從網絡上引入到本地項目, 供己使用.

NPM 包管理器

NPM 是隨同 Node.js 一塊兒安裝的 "包管理工具". 經過它, 全世界開發者們能夠簡單方便地互相分享和借鑑各自的 Node.js 模塊. 其讓整個 Node.js 社區生態變得繁榮熱鬧.

1*EiKXXsymsyfZ5X46uFG3Xw

NPM 常見的使用場景有如下幾種:

  • 容許用戶從 NPM 服務器下載別人編寫的第三方模塊到本地使用。
  • 容許用戶從 NPM 服務器下載並安裝別人編寫的命令行程序到本地使用。
  • 容許用戶將本身編寫的模塊或命令行程序上傳到NPM服務器供別人使用。

具體的使用方法網上有不少教程, 這裏就不贅述了. 不想自行查閱的話, 能夠直接參考下面的連接:

npm 官方文檔

npm 使用介紹 - 菜鳥教程


使用模塊

在瞭解了什麼是模塊以後, 讓咱們來看看如何在 Node.js 中實際應用模塊機制. 在使用上, 能夠很簡單的分爲三個步驟: 建立, 導出, 引入. 先建立一個模塊, 而後導出功能或數據, 模塊之間能夠互相引入導出的內容.

Node.js 提供了 exportsrequire 兩個對象,其中 exports 用於導出模塊, require 用於從外部引入另外一個模塊, 即獲取模塊的 exports 對象.

建立 & 導出模塊

先讓咱們來看看如何建立並把模塊的內容導出. 在 Node.js 中, 一個文件就是一個模塊. 建立模塊的方法就是建立一個文件.

經過 exports 對象來指定一個模塊的導出內容.

示例:

// 文件名: nameModule.js
var name = 'Garrik';

exports.setName = function(newName) {
name = newName;
}

exports.getName = function() {
return name;
}
複製代碼

在以上示例中, nameModule.js 文件經過 exports 對象將 setNamegetName 做爲模塊的訪問接口. 其餘的模塊能夠引入導出的 exports 對象, 直接訪問 exports 對象的成員函數.

引入模塊

在 Node.js 中, 經過 require 函數來引入外界模塊導出的內容. require 函數接受一個字符串做爲路徑參數, 函數根據這個字符串參數來進行模塊查找. 找到後會返回目標模塊導出的 exports 對象.

示例:

// 文件名: showNameModule.js
var nameModule = require('./nameModule.js');

console.log(nameModule.getName()); 
// 顯示: Garrik

nameModule.setName('Xiang');

console.log(nameModule.getName());
// 顯示: Xiang
複製代碼

上面示例中, 經過 require 引入了當前目錄下 nameModule.js 導出的 exports 對象, 並讓一個本地變量指向引入模塊的 exports 對象. 以後在 showNameModule.js 文件中就可使用 getNamesetName 這兩個方法了.

module.exports 和 exports 的區別

在使用 exports 對象導出內容時, 全部做爲對外訪問接口的屬性和方法都是定義在 exports 屬性上的. 上面的例子中 setNamegetName 方法都直接定義在 exports 對象上. 那若是想直接導出一個對象, 或者基礎類型值可不能夠呢?

可能有人會想可不能夠這樣寫:

var name = 'Garrik';

exports = name;
複製代碼

若是你試一下的話會發現, 最後引入的是一個空對象, 而不是你定義在 exports 上的東西.

在使用 exports 的時候只能往這個對象裏添加新的屬性和方法, 而不能對其直接賦值. 若是想直接導出一個對象, 或者基礎類型值要使用 module.exports 對象. 例如上面例子就能夠改寫成:

// 文件名: nameModule.js
var name = 'Garrik';

module.exports = {
    setName: function(newName) {
        name = newName;
    }, 
    getName: function() {
        return name;
    }
} 
複製代碼

這樣寫的話, 就導出了一整個對象, setNamegetName 方法是這個對象的成員函數. 而不是以前的 exports 對象了.

除此以外 module.exports 還能夠直接導出基礎類型值:

// 文件名: numMoule.js

var num = 123456;

module.exports = num;
複製代碼
// 文件名: showNum.js
var getNum = require('./numModule.js'); // showNum.js 和 numModule.js 在同一目錄下

console.log(getNum); // 結果: 123456
複製代碼

這種方式下, 導出的就直接是基礎類型的值.

可能仍是不少人在疑惑 exportsmodule.exports 區別和關係.

上面我說, 一個文件被另外一個模塊引入時, 會被作一些處理. 文件中代碼並不被 Node 執行, 而是被打包進一個函數中, 而後 Node 執行這個函數. 打包函數會被傳入 exportsrequiremodule__filename__dirname 這五個參數. 全部的這些參數都在 Node.js 執行函數時賦值, 而且只在當前的函數做用域中有效. 打包函數執行到最後, 返回 module.exports 對象.

其中, exportsmodule.exports 的引用, module 對象表明被打包進去的代碼自己. moduleexports 對象用於指定一個模塊的導出內容.

在模塊中定義外部可訪問接口的時候, 有兩個方法:

exports.name = 'Garrik';
複製代碼
module.exports = {name: 'Garrik'};
複製代碼

在使用 exports 的時候只能往這個對象裏添加新的屬性和方法, 而不能對其直接賦值. 由於直接賦值會打破其對 module.exports 的引用.

// 這是能夠的:
exports.name = 'Garrik';
exports.gender = 'Male';

// 這是不能夠的:
exports = {name: 'Garrik', gender: 'Male'};

// 應該用 module.exports:
module.exports = {name: 'Garrik', gender: 'Male'}
複製代碼

若是想直接導出一個對象, 或基本類型值, 應該使用 module.exports.

// 導出函數
module.exports = function(num) {
return num + 1;
};

// 導出基本類型值
module.exports = 123;
複製代碼

require 的路徑參數

在用 require 引入模塊時, 路徑參數可能有下面三種形式:

  • 相對路徑: ./ 開頭 或 ../ 開頭
  • 絕對路徑: / 開頭
  • 模塊名 (例如: http, fs, url)

根據參數不一樣, 加載方式也有區別.

絕對路徑, 或相對路徑

在指定了模塊路徑的狀況下, Node.js 會去指定的位置加載模塊. 但由於用 require 來加載模塊時能夠省略文件後綴, 在省略的狀況下, Node.js 會去猜想文件的類型.

比方說我要去 ./modules/ 目錄下加載一個 haha 模塊.

var haha = require('./modules/haha');
複製代碼

由於 haha 沒寫文件後綴, Node.js 將執行的操做順序爲:

  • 按 js 文件來執行(先找對應路徑當中是否有 haha.js 文件, 有就加載)
  • 按 json 文件來解析(若上面的 js 文件找不到時,則找對應路徑當中的 haha.json 文件來加載)
  • 按照預編譯好的 C++ 模塊來執行(尚未, 尋找對應路徑當中的 haha.node 文件來加載)
  • 若參數字符串爲一個目錄的路徑, 就是說 haha 爲一個目錄, 則先查找該文件夾下的 package.json 文件,而後再加載該文件當中 main 字段所指定的入口文件. 若 package.json 文件當中沒有 main 字段,或者根本沒有 package.json 文件,則再默認查找該文件夾下的 index.js 文件, 並做爲模塊來載入.
  • 要是尚未就拉倒吧!

無路徑, 直接模塊名:

在沒有路徑, 參數值直接爲一個模塊名的狀況下:

var haha = require('haha');
複製代碼
  • 若是 haha 是 Node.js 核心模塊就直接加載.
  • 若是是第三方模塊, 則依次從當前目錄中的 node_modules 目錄, 父級目錄中的 node_modules 目錄, 一直到根目錄下的 node_modules 目錄下去查找 haha 的所在. 如有兩個同名文件,則遵循就近原則。優先引入目錄順序靠前的模塊.
  • 若是找到的 haha 爲一個目錄, 則先查找該文件夾下的 package.json 文件,而後再加載該文件當中 main 字段所指定的入口文件. 若 package.json 文件當中沒有 main 字段,或者根本沒有 package.json 文件,則再默認查找該文件夾下的 index.js 文件, 並做爲模塊來載入.
  • 要是尚未就拉倒吧!

😆 好啦,今天的分享就告一段落啦。下一篇中,我會介紹如何實現一個 "Hello World" HTTP 服務器。

傳送門: Node.js 系列 - 搭建 "Hello World" HTTP 服務器

若是喜歡的話就點個關注吧!O(∩_∩)O 謝謝各位的支持❗️

相關文章
相關標籤/搜索