一、NodeJS的模塊機制補充
為了方便,Node為每個模塊提供一個exports變量,指向module.exports。這等同在每個模塊頭部,有一行這樣的命令。
var exports = module.exports;
於是我們可以直接在 exports 對象上添加方法,表示對外輸出的接口
根據使用方法來說:
通常exports方式使用方法是:
exports.[function name] = [function name]
moudle.exports方式使用方法是:
moudle.exports= [function name]
這樣使用兩者根本區別是
exports 返回的是模塊函數
module.exports 返回的是模塊對象本身,返回的是一個類
使用上的區別是 exports的方法可以直接調用 module.exports需要new對象之后才可以調用
導出多個成員(必須在對象中):
-
方法一(使用 exports):
exports.a = 123; exports.b = 'hello'; exports.c = () => { console.log('ccc'); }; exports.d = { foo: 'bar' };
-
方法二(使用module.exports):
module.exports = { add: (x, y) => { return x + y; }, str: 'hello' };
導出單個成員(拿到的直接就是 字符串, 數字 等):
但是的話,因為只能導出單個成員,所以會出現覆蓋情況,如下所示:
module.exports = 'hello'; // 以這個為准,后者會覆蓋前者 module.exports = (x, y) => { return x + y; };
exports是module.exports的一個引用,exports指向的是module.exports
也就是說exports的方法module.exports也是一定能完成的
exports.[function name] = [function name] moudle.exports= [function name]
所以,在使用上
** moudle.exports.[function name] = [function name] ** ** 是完全和 ** ** exports.[function name] = [function name] ** ** 相等的 **
但是我們通常還是推薦使用exports.[function name],各司其職,代碼邏輯清晰
總結:
-
每一個模塊中都有一個 module 對象, module 對象中有一個 exports 對象
-
我們可以把需要導出的成員都放到 module.exports 這個接口對象中,也就是 module.exports.xxx = xxx 的方式
-
但是,這樣顯得特別麻煩,為了方便操作,在每一個模塊中又提供了一個叫 exports 的成員
-
所以,有了這樣的等式: module.exports === exports
-
所以,對於:module.exports.xxx = xxx 的方式等價於 exports.xxx = xxx
-
當一個模塊需要導出單個成員的時候,必須要使用 module.exports = xxx
-
因為每個模塊最終向外 return 的是 module.exports,而 exports 只是 module.exports 的一個引用,所以即便你為 exports = xxx 重新賦值,也不會影響 module.exports
-
exports 和module.exports很少混用,一般只用一個
二、node中引入模塊的機制
在Node中引入模塊,需要經歷3個步驟
(1)路徑分析
(2)文件定位
(3)編譯執行
在Node中,模塊一般分為兩種
(1)Node提供的模塊,例如http、fs等,稱為核心模塊。核心模塊在node源代碼編譯的過程中就編譯進了二進制執行文件,在Node進程啟動的時候,部分核心模塊就直接加載進內存中了,因此這部分模塊是不用經歷上述的(2)(3)兩個步驟的,而且在路徑分析中是優先判斷的,因此加載速度最快。
(2)用戶自己編寫的模塊,稱為文件模塊。文件模塊是按需加載的,需要經歷上述的三個步驟,速度較慢。
核心模塊在Node.js源代碼編譯的時候編譯進了二進制執行文件,在nodejs啟動過程中,部分核心模塊直接加載進了內存中,所以這部分模塊引入時可以省略文件定位和編譯執行兩個步驟,所以加載的速度最快。另一類文件模塊是動態加載的,加載速度比核心模塊慢。但是Node.js對核心模塊和文件模塊都進行了緩存,於是在第二次require時,是不會有重復開銷的。其中原生模塊都被定義在lib這個目錄下面,文件模塊則不定性。
ps:核心模塊又分為兩部分,C/C++編寫的和Javascript編寫的,前者在源碼的src目錄下,后者則在lib目錄下。(lib/*.js)(其中lib/internal部分不提供給文件模塊)
require 方法的加載規則**
-
優先從緩存中加載
-
核心模塊
-
路徑形式的模塊
-
第三方模塊
與瀏覽器會緩存靜態腳本文件以提高頁面性能一樣,Node對引入過的模塊也會進行緩存。不同的地方是,node緩存的是編譯執行之后的對象而不是靜態文件。require()方法在對同一模塊的二次加載一律采用緩存優先的方式。但是對核心模塊的緩存檢查優先於對文件模塊的緩存檢查。
優先從緩存中加載測試
main.js:執行加載a.js模塊
require('./a')
a.js:執行加載b.js模塊,並輸出a被加載了
require('./b') console.log('a.js 被加載了')
b.js:輸出b被加載了
console.log('b.js 被加載了')
結果:
分析:
main去加載a.js,然后a在去加載b.js過程中,並沒有打印兩次 a.js被加載,Node會直接從require.cache中根據傳入的id,取出該對象的exports值,不會再次執行該模塊代碼。
測試2
核心模塊的加載
核心模塊的本質也是文件,核心模塊文件已經被編譯到了二進制文件中了,我們只需要按照名字來加載就可以了。如:
-
require(‘fs’)
-
require(‘http’)
路徑形式的模塊加載(自己寫的模塊加載)
我們說的路徑形式的模塊,其實就是加載自己寫的JS文件,有四種方式可以加載
var fooExports = require('./index') //相對路徑,常用 var fooExports = require('../index') //相對路徑,常用 var fooExports = require('/index') //根目錄,不常用 var fooExports = require('D:/demo/index') //根目錄,不常用
第三方模塊
-
凡是用到第三方模塊,都必須通過 npm 來下載
-
使用的時候就可以通過 require('包名') 的方式來進行加載才可以使用
-
不可能有任何一個第三方包和核心模塊的名字是一樣的
以 art-template為例分析require的加載過程
(0)測試准備,創建項目文件夾project,進入這個目錄的命令提示行
通過npm安裝 art-template模塊
安裝成功后,項目文件夾內會出現一個package-lock.json(包描述文件)和node_modules
創建main.js寫入內容
require('art-template') console.log('執行完成')
(1)執行main.js,分析require加載art-template模塊的過程
分析執行過程如下:
// 先找到當前文件所處目錄中的 node_modules 目錄
// node_modules/art-template
// node_modules/art-template/package.json 文件
// node_modules/art-template/package.json 文件中的 main 屬性
// main 屬性中就記錄了 art-template 的入口模塊index.js
// 然后加載使用這個第三方包
// 實際上最終加載的還是文件
(3) 如果 package.json 文件不存在或者 main 指定的入口模塊是也沒有 則 node 會自動找該目錄下的 index.js, 也就是說 index.js 會作為一個默認備選項
測試:在node_modules文件夾中創建a文件夾,package.json文件(包描述文件)、index.js文件
在main.js中require('a')執行后
(4) 如果以上所有任何一個條件都不成立,則會進入上一級目錄中的 node_modules 目錄查找 如果上一級還沒有,則繼續往上上一級查找, 如果直到當前磁盤根目錄還找不到,最后報錯: can not find module xxx var template = require('a')
測試效果
總結:
注意:我們一個項目有且只有一個 node_modules,放在項目根目錄中,這樣的話項目中所有的子目錄中的代碼都可以加載到第三方包,不會出現有多個 node_modules
模塊查找機制
優先從緩存加載 核心模塊 路徑形式的文件模塊 第三方模塊 找到node_modules/art-template/ 找到 node_modules/art-template/package.json 找到node_modules/art-template/package.json main 找到 index.js 備選項 進入上一級目錄找 node_modules
按照這個規則依次往上找,直到磁盤根目錄還找不到,最后報錯:Can not find moudle xxx 一個項目有且僅有一個 node_modules 而且是存放到項目的根目錄
三、包和 NPM
什么是包
由於 Node 是一套輕內核的平台,雖然提供了一系列的內置模塊,但是不足以滿足開發者的需求,於是乎出現了包(package)的概念: 與核心模塊類似,就是將一些預先設計好的功能或者說 API 封裝到一個文件夾,提供給開發者使用。
Node 本身並沒有太多的功能性 API,所以市面上涌現出大量的第三方人員開發出來的 Package。
NPM:Node Package Manager。官方鏈接: https://www.npmjs.com/
隨着時間的發展,NPM 出現了兩層概念:
-
一層含義是 Node 的開放式模塊登記和管理系統,亦可以說是一個生態圈,一個社區。
-
另一層含義是 Node 默認的模塊管理器,是一個命令行下工具,用來安裝和管理 Node 模塊。
這里學習使用的是npm命令工具。
nvm與npm區別
nvm就是nodejs version manage 叫做nodejs 版本管理,而nodejs有很多版本,場景如下:
1、而你手上開發的有多個項目又分別是不同的nodejs版本,咱們就可以用nvm輕松切換!
2、假設你正在開發的項目開始使用的nodejs版本是8.0,而現在因為某些原因,你需要升級 或者 降級 nodejs 版本,也可以使用 nvm 輕松切換npm全稱為Node Package Manager,是一個基於Node.js的包管理器,也是整個Node.js社區最流行、支持的第三方模塊最多的包管理器。
npm的初衷:JavaScript開發人員更容易分享和重用代碼。
npm的使用場景:
-
允許用戶獲取第三方包並使用。
-
允許用戶將自己編寫的包或命令行程序進行發布分享。
NPM 的安裝(不需要單獨安裝)
NPM 不需要單獨安裝。默認在安裝 Node 的時候,會連帶一起安裝 NPM:
nvm安裝node成功,npm失敗問題
通過nvm install 版本號 安裝你要的nodejs版本,必須是npm和nodejs都成功,因為有時候會npm或者nodejs不會下載成功,不成功的原因很多。
打開nvm安裝的文件夾,進入具體版本文件夾,有npm文件的才算安裝成功
安裝失敗情況,下圖是不行的,但是 你可以自己去下載一個nodejs版本,但后解壓后放復制到nvm目錄,注意命名,如:v11.11.0,這樣就不需要使用 nvm install 命令,
問題解決
其實安裝和使用的過程並不難,但是在安裝過程中,新一些的版本總是安裝npm不成功,導致我一度以為是自己的安裝出現問題或者環境變量和全局變量設置有問題,各種重裝和設置,后來發現有人說 目前發現 8.11以上版本的node版本對應的npm都沒法自動安裝,需要自己到npm官網( https://npm.taobao.org/mirrors/npm/)下載手動安裝對應的npm版本。 node和npm版對應關系可以在使用nvm install 版本號安裝過程中看到
下載完成,將解壓后的文件復制到,對應版本\node_modules目錄下,並重命名為npm(注意必須重命名為npm)
還需要將npm解壓后的bin文件夾下的四個文件復制到對應版本目錄下:
復制文件完成,測試npm
參考資料
[1]https://blog.csdn.net/fenfeidexiatian/article/details/96993384
[2]http://www.imooc.com/article/34483
[3]https://www.cnblogs.com/EricZLin/p/9289278.html
[4]https://www.infoq.cn/article/nodejs-module-mechanism/