Node.js的模塊機(jī)制使用了Common.js作為規(guī)范。Node.js中我們通過module.exports、require來導(dǎo)出和導(dǎo)入一個(gè)模塊。
模塊的分類
系統(tǒng)模塊
1、c/c++模塊,也叫做built-in內(nèi)建模塊,一般用于 native 模塊調(diào)用,在 require 出去
2、native模塊(核心模塊),node.js的內(nèi)置模塊,直接require即可,無需下載
文件模塊
我們自己定義的文件模塊,例如:
const book = require(./book) 復(fù)制代碼
第三方模塊
我們通過npm安裝的模塊就是第三方模塊比如koa、moment.js、express等
const koa = require(koa) const time = require(moment) 復(fù)制代碼
模塊的加載機(jī)制
上面我們說了node.js的模塊的構(gòu)成,下面我們來說一下加載這些模塊的過程是如何進(jìn)行的。
Node.js加載模塊的過程分為三步:?路徑分析、文件定位、編譯執(zhí)行
按照模塊的分類,我們按照一下順序進(jìn)行加載:
第一次加載時(shí):
系統(tǒng)緩存:模塊被執(zhí)行后會(huì)存入緩存,當(dāng)我們引用模塊時(shí),會(huì)首先查找模塊在緩存中是否存在,是的話直接引用緩存中的模塊。
系統(tǒng)模塊:這個(gè)優(yōu)先級(jí)僅次于緩存加載,部分的核心模塊已經(jīng)編譯成二進(jìn)制文件,省略了路徑分析和文件定位,直接加載到內(nèi)存中,系統(tǒng)模塊定義到node.js的源碼目錄的lib文件夾下,可以查看。
文件模塊:如果加載的是文件模塊,如果文件沒有加上擴(kuò)展名,會(huì)依次按照?.js?、?.json?、?.node?進(jìn)行擴(kuò)展名補(bǔ)足嘗試,那么?在嘗試的過程中也是以同步阻塞模式來判斷文件是否存在?,從性能優(yōu)化的角度來看待,?.json?、?.node?最好還是加上文件的擴(kuò)展名。
目錄作為模塊:這種情況發(fā)生在文件模塊加載過程中,也沒有找到,但是發(fā)現(xiàn)是一個(gè)目錄的情況,這個(gè)時(shí)候會(huì)將這個(gè)目錄當(dāng)作一個(gè)?包?來處理,找到目錄下的index.js;如果沒有index.js,因?yàn)镹ode 這塊采用了 Commonjs 規(guī)范,就會(huì)在項(xiàng)目根目錄查找 package.json 文件,取出文件中定義的 main 屬性?("main":"lib/hello.js")?描述的入口文件進(jìn)行加載,也沒加載到,則會(huì)拋出默認(rèn)錯(cuò)誤: Error: Cannot find module 'lib/hello.js'
node_modules目錄加載:1當(dāng)我們在系統(tǒng)模塊、文件模塊都找不到時(shí),Node.js會(huì)從當(dāng)前模塊的父目錄進(jìn)行查找,直到系統(tǒng)的根目錄。node會(huì)根據(jù)你引用的模塊名稱,在node_modules目錄下查找相同名稱的目錄下的package.json文件,根據(jù)package.json文件的main屬性指定的入口文件,拿到加載的模塊。
模塊的循環(huán)引用
問題一:假設(shè)有 a.js、b.js 兩個(gè)模塊相互引用,會(huì)有什么問題?是否為陷入死循環(huán)?
// a.jsconsole.log('a模塊start');exports.test = 1;undeclaredVariable = 'a模塊未聲明變量'const b = require('./b');console.log('a模塊加載完畢: b.test值:',b.test); // b.jsconsole.log('b模塊start');exports.test = 2;const a = require('./a');console.log('undeclaredVariable: ', undeclaredVariable);console.log('b模塊加載完畢: a.test值:', a.test); 復(fù)制代碼
解答:啟動(dòng)?a.js?的時(shí)候,會(huì)加載?b.js?,那么在?b.js?中又加載了?a.js?,但是此時(shí)?a.js?模塊還沒有執(zhí)行完,返回的是一個(gè)?a.js?模塊的?exports?對象?未完成的副本?給到?b.js?模塊(因此是不會(huì)陷入死循環(huán)的)。然后?b.js?完成加載之后將?exports?對象提供給了?a.js?模塊
對象引用關(guān)系
面試時(shí),考察的最多的可能就是module.exports 與 exports 的區(qū)別?
exports相當(dāng)于module.exports
const exports = module.exports
復(fù)制代碼
但是要注意不能改變exports的指向,我們可以通過exports.text = 'a',來導(dǎo)出一個(gè)變量,但是不能直接給exports賦值;像下邊的例子就是不可行的,這樣會(huì)改變exports指向
// 錯(cuò)誤的寫法
exports ={ text: '123' , a: '1', b: '1'? }
// 正確的寫法
module.exports = { text: '123' , a: '1', b: '1'? }
復(fù)制代碼
詳細(xì)解答:
每個(gè)模塊中都有一個(gè)module對象,module對象有一個(gè)exports對象,我們可以把需要導(dǎo)出的成員都掛在module.exports接口對象上,也就是module.exports.xxx=xxx這種方式。但是每次都以這種方式的話就會(huì)很麻煩,點(diǎn)兒的太多了,所以node為了方便,同事在每一個(gè)模塊中提供了一個(gè)成員:exports
exports === module.exports
所以,我們可以通過exports.xxx=xxx的方式來導(dǎo)出對象,但是當(dāng)我們只導(dǎo)出一個(gè)對象時(shí),我們必須使用http://module.exports.xxx?= xxx的方式來導(dǎo)出
我們給exports重新賦值是不生效的,因?yàn)槟K最終導(dǎo)出的是 module.exports而exports只是module.exports的一個(gè)引用而已,所以我們即使為exports賦值,也不會(huì)影響module.exports。
作為從事web開發(fā)多年的老司機(jī),整理了不少學(xué)習(xí)資料,每天晚上都會(huì)開設(shè)Web,vue,node,JS直播課,課程會(huì)講到軟件的使用以及項(xiàng)目的實(shí)戰(zhàn)開發(fā),想正兒八經(jīng)學(xué)習(xí)技術(shù)的小伙伴:可以關(guān)注小編--查看主頁--添加一起學(xué)習(xí)!