有時候我們禁用緩存,以便及時看到最新的修改。在這種情況下,可以通過給 Require.js 的配置對象添加一個隨機的查詢參數來防止緩存。例如:
require.config({
urlArgs: "bust=" + (new Date()).getTime()
});
require(["module"], function(module) {
// 模塊邏輯
});
這會在每次加載模塊時,給腳本的 URL 添加一個不同的時間戳參數,迫使瀏覽器重新下載和執行模塊文件,以確保獲取最新的代碼。
- 如果引用的是 .js 文件
- fs模塊同步讀取文件
- 包裝fs模塊同步讀取的內容
- 調用 vm 的 runInThisContext 方法 將 包裝字符串 解析為 javascript
- 調用匿名函數,并傳參,最后返回 module.exports 的賦值。
- 如果引用的是 .json 文件
- 經過fs模塊同步讀取文件以后用 JSON.parse() 解析返回結果。
- 如果引用的是 .node 文件
- 則經過 dlopen()方法加載最后編譯生成的對象,并且返回結果。
加載規則參考地址:
https://www.jianshu.com/p/a309864c8357
https://blog.csdn.net/u010476739/article/details/80925128
https://www.cnblogs.com/zhoulixue/p/8757546.html
http://www.javashuo.com/article/p-bywkbpzj-d.html
https://juejin.cn/post/6844903704123211783
加載的模塊類型:
1、原生內置模塊
存在:原生內置模塊緩沖區中,直接返回緩存結果,不執行原生內置模塊代碼
不存在:原生內置模塊緩沖區中,執行原生內置模塊代碼,把模塊加入緩沖區,并返回執行結果
2、路徑文件模塊
(1)、構建文件絕對路徑列表
存在后綴名(.js | .json | .node)
使用path.resolve("路徑") 拼接一個絕對路徑,推入文件絕對路徑列表
不存在后綴名
使用 path.resolve("路徑"+".js") 拼接一個絕對路徑的 .js 文件,推入文件絕對路徑列表
使用 path.resolve("路徑"+".json") 拼接一個絕對路徑的 .json 文件,推入文件絕對路徑列表
使用 path.resolve("路徑"+".node") 拼接一個絕對路徑的 .node 文件,推入文件絕對路徑列表
是否存在 path.resolve("路徑") 目錄
存在
是否存在package.json文件
存在
是否存在main屬性(若main沒指定后綴名,那會使用 .js | .json | .node,按照順序查找)
存在
使用path.resolve(path.jon("路徑",'main屬性值')) 拼接一個絕對路徑的文件,推入 文件絕對路徑列表
不存在
使用path.resolve(path.jon("路徑",'index.js')) 拼接一個絕對路徑的 .js 文件,推入 文件絕對路徑列表
使用path.resolve(path.jon("路徑",'index.json')) 拼接一個絕對路徑的 .json 文件,推入 文件絕對路徑列表
使用path.resolve(path.jon("路徑",'index.node')) 拼接一個絕對路徑的 .node 文件,推入 文件絕對路徑列表
不存在
使用path.resolve(path.jon("路徑",'index.js')) 拼接一個絕對路徑的 .js 文件,推入 文件絕對路徑列表
使用path.resolve(path.jon("路徑",'index.json')) 拼接一個絕對路徑的 .json 文件,推入 文件絕對路徑列表
使用path.resolve(path.jon("路徑",'index.node')) 拼接一個絕對路徑的 .node 文件,推入 文件絕對路徑列表
(2)、遍歷文件絕對路徑列表 ,查看是否在require.cache['文件路徑']緩沖區中
存在
讀取緩沖區中的模塊,不執行文件模塊的代碼,直接返回緩存的結果(require.cache['文件路徑'].exports)
不存在
報錯找不到模塊
3、第三方包模塊
(1)、獲取模塊可能存在的目錄列表 module.paths
(2)、構建 文件絕對路徑列表
遍歷 目錄列表 module.paths 第一個元素
使用(path.join(目錄元素,包名+".js") 拼接一個絕對路徑的 .js 文件,推入 文件絕對路徑列表
使用(path.join(目錄元素,包名+".json") 拼接一個絕對路徑的 .json 文件,推入 文件絕對路徑列表
使用(path.join(目錄元素,包名+".node") 拼接一個絕對路徑的 .node 文件,推入 文件絕對路徑列表
判斷 path.join(目錄元素,包名) 目錄是否存
存在
判斷目錄下是否存在package.json文件
存在
是否存在main屬性
存在
使用 path.jon(目錄元素",包名,'main屬性值')) 拼接一個絕對路徑的文件,推入 文件絕對路徑列表
不存在
使用 path.jon(目錄元素",包名,'index.js')) 拼接一個絕對路徑的文件,推入 文件絕對路徑列表
使用 path.jon(目錄元素",包名,'index.json')) 拼接一個絕對路徑的文件,推入 文件絕對路徑列表
使用 path.jon(目錄元素",包名,'index.node)) 拼接一個絕對路徑的文件,推入 文件絕對路徑列表
不存在
繼續遍歷 目錄列表 module.paths
不存在
繼續遍歷 目錄列表 module.paths
(3)、遍歷文件絕對路徑列表,查看是否在require.cache['文件路徑']緩沖區中
存在
讀取緩沖區中的模塊,不執行文件模塊的代碼,直接返回緩存的結果(require.cache['文件路徑'].exports)
不存在
報錯找不到模塊
謝 @曉之車 在我上一篇分享(模塊化中的import與require及區別)中的提問,我也特定研究了一番,這里是一篇很早的博文,一起來看下作者的分析
Node.js也使用了CommonJS模塊機制,最近在InfoQ上有一篇文章討論了這方面的問題。這篇文章提到Node.js在載入模塊時,如果之前該模塊已經加載過則不會有重復開銷,因為模塊加載有緩存機制。這篇文章是我初審的,當時也正好在思考Jscex在Node.js使用時的模塊化問題,但研究了它的規則之后,我發現在某些情況下還是可能加載多次。現在我們便來分析這個問題。
當我們使用require方法加載另一模塊的時候,Node.js會去查詢一系列的目錄。我們可以從module.paths中得到這些路徑,例如:
[ '/Users/jeffz/Projects/node-test/node_modules',
'/Users/jeffz/Projects/node_modules',
'/Users/jeffz/node_modules',
'/Users/node_modules',
'/node_modules']
這里是我在運行/User/jeffz/Projects/node-test目錄下一個模塊時得到的結果。可見,Node.js會從當前模塊所在目錄的node_modules(這里怎么不遵守Unix習慣,而使用了下劃線呢?)開始找起,如果沒找到再會去找上級目錄的node_modules,直到根目錄為止。當然,實際情況下還會有NODE_PATH環境變量標識的目錄等等。當模塊的位置確定之后,Node.js便會查看這個位置的模塊是否已經被加載,如果已加載,則直接返回。
簡單地說,Node.js是根據模塊所在路徑來緩存模塊的。
這么看來,“相同模塊是否會被加載多次”這個問題,其實就演變成了“相同模塊是否會出現在不同路徑里”。簡單想來這似乎不太可能,因為如果我們要使用某個模塊的時候,它的位置總是確定的。例如,使用npm安裝的模塊,總是會出現在當前目錄的node_modules里,加載時總是會找到相同的路徑。那么,在“間接”依賴相同模塊的情況下呢?
例如我們想要使用Express框架,于是使用npm來安裝,便會得到:
$ npm install express
express@2.5.2 ./node_modules/express
├── mkdirp@0.0.7
├── qs@0.4.0
├── mime@1.2.4
└── connect@1.8.5
可見,Express依賴了其他一些模塊,它們都存放在express模塊自己的目錄里面,例如./node_modules/express/node_modules/mime。好,假如我們項目自身也要使用mime項目,我們自然也可以使用npm來安裝:
$ npm install mime
mime@1.2.4 ./node_modules/mime
于是我們最終得到的是這樣的結構:
./node_modules
├── mime
└── express
└── node_modules
├── mkdirp
├── qs
├── mime
└── connect
請注意,這里的mime模塊便出現在兩個位置上,它們名稱版本都一致,完全是一個模塊。那么試想,如果我們在自己的代碼里加載的mime模塊,以及express內部加載的mime模塊是同一個嗎?顯然不是,可見,在這里相同的模塊被重復加載了兩次,產生了兩個模塊“實例”。
這種重復加載在一般情況下不會有太大問題,最多內存占用大一點而已,不會影響程序的正確性。但是,我們也可以輕易設想到一些意外的情況。例如,在Jscex中,每個Task對象我都會給定一個ID,不斷增長。要實現這點我們需要維護一個“種子”,全局唯一。之前這個種子定義在閉包內部,但由于Jscex模塊會被加載多次,這樣從不同模塊“實例”生成的Task對象,它們的ID便有可能重復。當然,解決這個問題也并不困難,只需要將種子定義在根對象上即可,不同的模塊“實例”共享相同的根對象。
還有個問題可能就顯得隱蔽些了,我們可以通過一個簡單的實驗來觀察結果。我們先來定義一個jeffz-a模塊,其中暴露出一個MyType類型:
module.exports.MyType=function () { }
然后將其發布到npm上。然后再寫一個jeffz-b模塊,依賴jeffz-a,并將jeffz-a中定義的MyType類型直接暴露出去:
module.exports.MyType=require("jeffz-a").MyType;
接著將jeffz-b也發布置npm上。再重新寫一個測試模塊,使用npm安裝jeffz-a和jeffz-b,最終目錄會是這樣的:
./node_modules
├── jeffz-a
└── jeffz-b
└── node_modules
└── jeffz-a
在測試模塊內,我們來測試實例與類型之間的關系:
var a=require("jeffz-a");
var b=require("jeffz-b");
console.log(new a.MyType() instanceof a.MyType); // true
console.log(new b.MyType() instanceof b.MyType); // true
console.log(new a.MyType() instanceof b.MyType); // false
console.log(new b.MyType() instanceof a.MyType); // false
從表面上看,jeffz-b和jeffz-a暴露出的應該是相同的MyType類型,它們的對象通過instanceof相互判斷應該都返回true,但實際上由于jeffz-b中的jeffz-a,與我們直接加載的jeffz-a模塊是不同的實例,因此MyType類型自然也不是同一個了。
這對于Jscex的影響在于,Jscex的異步模塊在取消時,原本是通過判斷異常對象是否為CanceledError類型來決定Task的狀態為cancelled還是faulted。但由于Node.js可能會將相同的模塊加載為多個實例,因此即便拋出的的確是某個實例的CancelledError,也無法通過另一個實例內部的判斷。因此,目前Jscex的判斷方式修改為檢查異常對象的isCancellation字段,簡單地解決了這個問題。
當然,Node.js這種“重復加載”的影響也并非完全是負面的,至少它天然的解決了多版本共存的問題。例如,express v2.5.2依賴mime v1.2.4,但我們程序自身又想使用mime v1.2.5。此時,express內部自然使用mime v1.2.4,而我們自己的程序使用的便是mime v1.2.5。
有些情況下您可能也想避免這種重復加載,這就必須手動地刪除模塊內部被間接依賴的模塊,將其移動到模塊查詢路徑的公用部分上了。就目前看來,這些操作必須手動進行,因為npm在安裝模塊時不會關心依賴的模塊是否已經安裝過了(例如在NODE_PATH環境變量標識的路徑里),它一定會重新下載所有依賴的模塊。可惜如果您使用的是托管形式的Node.js服務,則很有可能無法做到這一點。
原文地址:Node.js中相同模塊是否會被加載多次?-CSDN博客
*請認真填寫需求信息,我們會在24小時內與您取得聯系。