日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
NodeJS中的模塊是單例的嗎?

本文翻譯自 Lazlojuly 的 are-node-js-modules-singletons。

本文從屬于筆者的NodeJS入門與***實(shí)踐中的NodeJS 基礎(chǔ)系列文章,包括NodeJS 入門、NodeJS 模塊導(dǎo)出與解析、NodeJS IOStream、NodeJS HTTPS這幾部分。

筆者之前在使用require導(dǎo)入模塊時(shí),特別是在導(dǎo)入有狀態(tài)的模塊時(shí),筆者會(huì)考慮其是否在多次導(dǎo)入情況下依然保持單例特性,或者說對(duì)于同一個(gè)文件在不同路徑下導(dǎo)入時(shí),是否能夠識(shí)別為一致?本文即是對(duì)該特性進(jìn)行解析。

 NodeJS的模塊默認(rèn)情況下是單例性質(zhì)的,不過其并不能保證如我們編程時(shí)設(shè)想的那樣一定是單例,根據(jù)NodeJS的官方文檔中描述,某個(gè)模塊導(dǎo)入是否為單例受以下兩個(gè)因素的影響:

  • Node 模塊的緩存機(jī)制是大小寫敏感的,譬如如果你require('/foo')與require('/FOO')會(huì)返回兩個(gè)不同的對(duì)象,盡管你的foo與FOO是完全相同的文件。
  • 模塊是基于其被解析得到的文件名進(jìn)行緩存的,鑒于不同的模塊會(huì)依賴于其被調(diào)用的路徑進(jìn)行緩存鑒別,因此并不能保證你使用require('foo')會(huì)永遠(yuǎn)返回相同的對(duì)象,可能會(huì)根據(jù)不同的文件路徑得到不同的對(duì)象。

創(chuàng)建新的NodeJS模塊

根據(jù)NodeJS文檔所述,文件和模塊是一一對(duì)應(yīng)的關(guān)系。這個(gè)也是解釋上文提及的模塊緩存機(jī)制的基礎(chǔ),我們首先創(chuàng)建一個(gè)簡單的模塊:

 
 
  1. // counter.js 
  2. let value = 0
  3. module.exports = {
  4.   increment: () => value++,
  5.   get: () => value,
  6. }

 在counter.js中我們創(chuàng)建了某個(gè)私有變量,并且只能通過公共的increment與get方法進(jìn)行操作。在應(yīng)用中我們可以如下方法使用該模塊:

 
 
  1. // app.js
  2. const counter = require(‘./counter.js’)
  3. counter.increment()
  4. counter.increment()
  5. console.log(counter.get()) // prints 2
  6. console.log(counter.value) // prints undefined as value is private 

Module Caching

NodeJS會(huì)在***次導(dǎo)入某個(gè)模塊之后將該模塊進(jìn)行緩存,在官方文檔中有如下描述:

Every call to require(‘foo’) will get exactly the same object returned, if it would resolve to the same file.

我們也可以通過如下簡單的例子來驗(yàn)證這句話:

 
 
  1. // app-singleton.js
  2. const counter1 = require(‘./counter.js’)
  3. const counter2 = require(‘./counter.js’)
  4. counter1.increment()
  5. counter1.increment()
  6. counter2.increment()
  7. console.log(counter1.get()) // prints 3
  8. console.log(counter2.get()) // also prints 3 

可以看出盡管我們兩次導(dǎo)入了該模塊,但是還是指向了同一個(gè)對(duì)象。不過并不是每次我們導(dǎo)入同一個(gè)模塊時(shí),都會(huì)得到相同的對(duì)象。在NodeJS中,模塊對(duì)象有個(gè)內(nèi)置的方法:Module._resolveFilename(),其負(fù)責(zé)尋找require中合適的模塊,在找到正確的文件之后,會(huì)根據(jù)其文件名作為緩存的鍵名。官方的搜索算法偽代碼為:

 
 
  1. require(X) from module at path Y
  2. 1. If X is a core module,
  3.    a. return the core module
  4.    b. STOP
  5. 2. If X begins with './' or '/' or '../'
  6. a. LOAD_AS_FILE(Y + X)
  7.       1. If X is a file, load X as JavaScript text.  STOP
  8.       2. If X.js is a file, load X.js as JavaScript text.  STOP
  9.       3...
  10.       4...
  11. b. LOAD_AS_DIRECTORY(Y + X)
  12.       1. If X/package.json is a file,
  13.          a. Parse X/package.json, and look for "main" field.
  14.          b. let M = X + (json main field)
  15.          c. LOAD_AS_FILE(M)
  16.       2. If X/index.js is a file, load X/index.js as JS text.  STOP
  17.       3...
  18.       4...
  19. 3. LOAD_NODE_MODULES(X, dirname(Y))
  20. 4. THROW "not found" 

簡單來說,加載的邏輯或者說優(yōu)先級(jí)為:

  • 優(yōu)先判斷是不是核心模塊
  • 如果不是核心模塊則搜索node_modules
  • 否則在相對(duì)路徑中進(jìn)行搜索

解析之后的文件名可以根據(jù)module對(duì)象或得到:

 
 
  1. // counter-debug.js
  2. console.log(module.filename) // prints absolute path to counter.js
  3. console.log(__filename) // prints same as above
  4. // i get: "/Users/laz/repos/medium/modules/counter-debug.js"
  5. let value = 0
  6. module.exports = {
  7.   increment: () => value++,
  8.   get: () => value, 

在上述的例子中我們可以看出,解析得到的文件名即使被加載模塊的絕對(duì)路徑。而根據(jù)文件與模塊一一映射的原則,我們可以得出下面兩個(gè)會(huì)破壞模塊導(dǎo)入單例性的特例。

Case Sensitivity

在大小寫敏感的文件系統(tǒng)中或者操作系統(tǒng)中,不同的解析之后的文件可能會(huì)指向相同的文件,但是其緩存鍵名會(huì)不一致,即不同的導(dǎo)入會(huì)生成不同的對(duì)象。

 
 
  1. // app-no-singleton-1.js
  2. const counter1 = require('./counter.js')
  3. const counter2 = require('./COUNTER.js')
  4. counter1.increment()
  5. console.log(counter1.get()) // prints 1
  6. console.log(counter2.get()) // prints 0, not same object as counter1
  7. /* 
  8. We have two different resolved filenames:
  9. - “Users/laz/repos/medium/modules/counter.js”
  10. - “Users/laz/repos/medium/modules/COUNTER.js”
  11. */ 

在上面的例子中,我們分別用counter、COUNTER這僅僅是大小寫不同的方式導(dǎo)入相同的某個(gè)文件,如果是在某個(gè)大小寫敏感的系統(tǒng)中,譬如UBUNTU中會(huì)直接拋出異常:

 解析為不同的文件名

當(dāng)我們使用require(x)并且x不屬于核心模塊時(shí),其會(huì)自動(dòng)搜索node_modules文件夾。而在npm3之前,項(xiàng)目會(huì)以嵌套的方式安裝依賴。因此當(dāng)我們的項(xiàng)目依賴module-a與module-b,并且module-a與module-b也相互依賴時(shí),其會(huì)生成如下文件路徑格式:

 
 
  1. // npm2 installed dependencies in nested way
  2. app.js
  3. package.json
  4. node_modules/
  5. |---module-a/index.js
  6. |---module-b/index.js
  7.     |---node_modules
  8.         |---module-a/index.js 

這樣的話,我們對(duì)于同一個(gè)模塊就有兩個(gè)副本,那當(dāng)我們?cè)趹?yīng)用中導(dǎo)入module-a時(shí),豈會(huì)載入如下文件:

 
 
  1.  // app.js
  2. const moduleA = require(‘module-a’)
  3. loads: “/node_modules/module-a/index.js” 

而從module-b中載入module-a時(shí),其載入的是如下文件:

 
 
  1.  // /node_modules/module-b/index.js
  2. const moduleA = require(‘module-a’)
  3. loads “/node_modules/module-b/node_modules/module-a/index.js” 

不過在npm3之后,其以扁平化方式進(jìn)行文件加載,其文件目錄結(jié)構(gòu)如下所示:

 
 
  1.  // npm3 flattens secondary dependencies by installing in same folder
  2. app.js
  3. package.json
  4. node_modules/
  5. |---module-a/index.js
  6. |---module-b/index.js 

不過此時(shí)就存在另一個(gè)場(chǎng)景,即我們應(yīng)用本身依賴module-a@v1.1與module-b,而module-b又依賴于module-a@v1.2,在這種情況下還是會(huì)采用類似于npm3之前的嵌套式目錄結(jié)構(gòu)。這樣的話對(duì)于module-a一樣會(huì)產(chǎn)生不同的對(duì)象,不過此時(shí)本身就是不同的文件了,因此相互之間不會(huì)產(chǎn)生沖突。


網(wǎng)頁標(biāo)題:NodeJS中的模塊是單例的嗎?
當(dāng)前網(wǎng)址:http://www.dlmjj.cn/article/dhiejop.html