本文由一個小故事來和大家分享關於node.js和macOS之間的故事,希望能幫助大家。
喬治G在他的電腦上做了一個小測試,但結果和預期的大不相同。
那麼我們先來看看這個小測試都寫了什麼:
總共三個文件,程式碼總計不超過15行
<span style="font-size: 14px;">class Parent {}<br><br>module.exports = Parent<br></span>
<span style="font-size: 14px;">//加载时把模块文件名首字母大写了(不正确的)<br/>const Parent = require('./Parent')<br/><br/>class Son extends Parent {}<br/><br/>module.exports = Son<br/></span>
<span style="font-size: 14px;">//加载时把模块名首字母大写(不正确的)<br/>const ParentIncorrect = require('./Parent')<br/>//通过正确的模块文件名加载(正确)<br/>const Parent = require('./parent')<br/><br/>const Son = require('./son')<br/><br/>const ss = new Son()<br/><br/>//测试结果<br/>console.log(ss instanceof Parent) // false<br/>console.log(ss instanceof ParentIncorrect) // true<br/></span>
喬治G同學有以下問題:
<span style="font-size: 14px;">son.js</span>
和<span style="font-size: 14px;">test.js</span>
裡都有錯誤的檔名(大小寫問題)引用,為什麼不報錯?
測試結果,為什麼 <span style="font-size: 14px;">ss instanceof ParentIncorrect === true</span>
?不報錯我忍了,為什麼還認賊作父,說自己是那個透過不正確名字載入出來的模組的instance?
如果同學你對上述問題已經了然於胸,恭喜你,文能提筆安天,武能上馬定乾坤;上炕認識娘們,下炕認識鞋!
但如果你也不是很清楚為什麼?那麼好了,我有的說,你有的看。
其實斷症(裝逼範兒的debug)之法和中醫看病也有相似指出,望、聞、問、切四招可以按需選取一二來尋求答案。
程式碼不多,看了一會兒,即使沒有我的註釋,相信仔細的同學也都發現真正的文件名和程式碼中引入時有出入的,那麼這裡肯定是有問題的,問題記住,我們繼續
#這個就算了,程式碼我也聞不出個什麼鬼來
#來吧,軟體工程裡很重要的一環,就是溝通,不見得是和遇到bug的同事,可能是自己,可能是QA,當然也可能是PM或是你的老闆。你沒問出自己想知道的問題;他沒說清楚自己要回答的;都完蛋。 。 。 。
那我想知道什麼呢?以下兩件事作為debug的入口比較合理:
作業系統
##運行環境+ 版本<span style="font-size: 14px;"></span>
你怎麼測試的,命令列還是其他什麼手段<span style="font-size: 14px;"></span>
答曰:macOS; <span style="font-size: 14px;"></span>node.js > 8.0<span style="font-size: 14px;"></span>
# ;命令列<span style="font-size: 14px;"></span>##node test.js<span style="font-size: 14px;"> </span>
<span style="font-size: 14px;"></span>debug<span style="font-size: 14px;"></span> 過程,我會假裝這下面的所有事情我事先都是不知道的)
<span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span>
準備運行環境<span style="font-size: 14px;"></span>#node.js > 9.3.0<span style="font-size: 14px;">## ,完畢</span>
<span style="font-size: 14px;"></span>復刻程式碼,完畢
<span style="font-size: 14px;"></span>#運行,日了狗,果然沒報錯,而且運行結果就是喬治G說的那樣。
<span style="font-size: 14px;"></span>為了證明我沒瞎,我又嘗試在
#test.js<span style="font-size: 14px;"> 裡</span>
<span style="font-size: 14px;"></span><span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span> <span style="font-size: 14px;">require</span>
了一個壓根不存在的檔案
#require('./nidayede')<span style="font-size: 14px;"></span> ,執行程式碼。 <span style="font-size: 14px;"></span>
還好這次報錯了
Error: Cannot find module './nidayede'<span style="font-size: 14px;"></span>
,所以我沒瘋。這點真令人高興。 ############於是有了第一個問題#######會不會跟作業系統有關係?來我們再找台<span style="font-size: 14px;">windows</span>
試試,果然,到了<span style="font-size: 14px;">windows</span>
## 上,大小寫問題就是個問題了, <span style="font-size: 14px;"></span>Error: Cannot find module './Parent'<span style="font-size: 14px;"></span>
。 <span style="font-size: 14px;"></span>
那麼 <span style="font-size: 14px;"></span>macOS<span style="font-size: 14px;"></span>
# 到底在做什麼?連個大小寫都分不出來麼?於是趕緊<span style="font-size: 14px;"></span>google<span style="font-size: 14px;"></span>
(別問我為什麼不baidu)<span style="font-size: 14px;"></span>
OS X<span style="font-size: 14px;"></span> 預設用了
<span style="font-size: 14px;"></span>case-insensitive<span style="font-size: 14px;"></span> 的文件系統( 詳細文件)。
<span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span>
更多解釋, 來,走你所以,這就是你不報錯的理由? (對
<span style="font-size: 14px;"></span>node.js<span style="font-size: 14px;"></span> 指責道),但這就是全部真相了。
<span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span>
那認賊作父又是個什麼鬼?<span style="font-size: 14px;"></span>node.js<span style="font-size: 14px;"></span> 裡面有什麼緩存,是那個東西引起的麼?於是抱著試試看的心情,我把
<span style="font-size: 14px;"></span>const ParentIncorrect = require('./Parent')<span style="font-size: 14px;"></span> 和
<span style="font-size: 14px;"></span>const Parent = require('./parent')<span style="font-size: 14px;"></span> 換了下位置,心想,這樣最先按照正確的名字加載,會不會就對了呢?
<span style="font-size: 14px;"></span>
還是不對 。靠猜和裝逼是不能夠真正解決問題的
那比比<span style="font-size: 14px;"></span>ParentIncorrect<span style="font-size: 14px;"></span> 和
<span style="font-size: 14px;"></span><span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span><span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span><span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span>
<span style="font-size: 14px;"></span><span style="font-size: 14px;"></span>Parent
<span style="font-size: 14px;"></span> 呢?於是我寫了<span style="font-size: 14px;"></span>console.log(ParentIncorrect === Parent)
<span style="font-size: 14px;"></span> ,結果是<span style="font-size: 14px;"></span>false
<span style="font-size: 14px;"></span> 。所以他兩個真的不是同一個東西,那麼說明問題可能在引入的部分嘍?
<span style="font-size: 14px;"></span>於是一個裝逼看<span style="font-size: 14px;"></span>node.js
<span style="font-size: 14px;"></span> 原始碼的想法誕生了(其實不看,問題最終也能想明白)。 日了狗,懷著忐忑的心情,終於<span style="font-size: 14px;"></span>clone
<span style="font-size: 14px;"></span># 了一把<span style="font-size: 14px;">##node.js</span>
# 原始碼(花了好久,真tm慢)
來,我們一起進入神秘的<span style="font-size: 14px;"></span>
node.js
原始碼世界。既然我們的問題是有關<span style="font-size: 14px;">require</span>
的,那就從她開始吧,不過找到
<span style="font-size: 14px;"></span> 定義的過程需要點耐心,這裡不詳述,只說查找的順序吧
############src/node_main.cc => src/node. cc => lib/internal/bootstrap_node.js => lib/module.js###### ########找到咯,就是這個##########lib/ module.js######### ,進入正題:#############lib/module.js => require#######<span style="font-size: 14px;">Module.prototype.require = function(path) {<br/> assert(path, 'missing path');<br/> assert(typeof path === 'string', 'path must be a string');<br/> return Module._load(path, this, /* isMain */ false);<br/>};<br/></span>
好像没什么卵用,对不对?她就调用了另一个方法 <span style="font-size: 14px;">_load</span>
,永不放弃,继续
lib/module.js => _load
<span style="font-size: 14px;">Module._load = function(request, parent, isMain) {<br/> //debug代码,么卵用,跳过<br/> if (parent) {<br/> debug('Module._load REQUEST %s parent: %s', request, parent.id);<br/> }<br/><br/> if (isMain && experimentalModules) {<br/> //...<br/> //...<br/> //这段是给ES module用的,不看了啊<br/> }<br/><br/> //获取模块的完整路径<br/> var filename = Module._resolveFilename(request, parent, isMain);<br/><br/> //缓存在这里啊?好激动有没有?!?终于见到她老人家了<br/> //原来这是这样的,简单的一批,毫无神秘感啊有木有<br/> var cachedModule = Module._cache[filename];<br/> if (cachedModule) {<br/> updateChildren(parent, cachedModule, true);<br/> return cachedModule.exports;<br/> }<br/><br/> //加载native但非内部module的,不看<br/> if (NativeModule.nonInternalExists(filename)) {<br/> debug('load native module %s', request);<br/> return NativeModule.require(filename);<br/> }<br/><br/> //构造全新Module实例了<br/> var module = new Module(filename, parent);<br/><br/> if (isMain) {<br/> process.mainModule = module;<br/> module.id = '.';<br/> }<br/><br/> //先把实例引用加缓存里<br/> Module._cache[filename] = module;<br/><br/> //尝试加载模块了<br/> tryModuleLoad(module, filename);<br/><br/> return module.exports;<br/>};<br/></span>
似乎到这里差不多了,不过我们再深入看看 <span style="font-size: 14px;">tryModuleLoad</span>
lib/module.js => tryModuleLoad
<span style="font-size: 14px;">function tryModuleLoad(module, filename) {<br/> var threw = true;<br/> try {<br/> //加载模块<br/> module.load(filename);<br/> threw = false;<br/> } finally {<br/> //要是加载失败,从缓存里删除<br/> if (threw) {<br/> delete Module._cache[filename];<br/> }<br/> }<br/>}<br/></span>
接下来就是真正的 <span style="font-size: 14px;">load</span>
了,要不我们先停一停?
好了,分析问题的关键在于不忘初心,虽然到目前为止我们前进的比较顺利,也很爽对不对?。但我们的此行的目的并不是爽,好像是有个什么疑惑哦!于是,我们再次梳理下问题:
<span style="font-size: 14px;">son.js</span>
里用首字母大写(不正确)的模块名引用了 <span style="font-size: 14px;">parent.js</span>
<span style="font-size: 14px;">test.js</span>
里,引用了两次 <span style="font-size: 14px;">parent.js</span>
,一次用完全一致的模块名;一次用首字母大写的模块名。结果发现 <span style="font-size: 14px;">son instanceof require('./parent') === false</span>
既然没报错的问题前面已经解决了,那么,现在看起来就是加载模块这个部分可能出问题了,那么问题到底是什么?我们怎么验证呢?
这个时候我看到了这么一句话 <span style="font-size: 14px;">var cachedModule = Module._cache[filename];</span>
,文件名是作为缓存的 <span style="font-size: 14px;">key</span>
,来吧,是时候看看 <span style="font-size: 14px;">Module._cache</span>
里存的模块 <span style="font-size: 14px;">key</span>
都是什么牛鬼蛇神了,打出来看看吧,于是我在 <span style="font-size: 14px;">test.js</span>
里最后面加了一句 <span style="font-size: 14px;">console.log(Object.keys(require.cache))</span>
,我们看看打出了什么结果
<span style="font-size: 14px;">false<br/>true<br/>[ '/Users/admin/codes/test/index.js',<br/> '/Users/admin/codes/test/Parent.js',<br/> '/Users/admin/codes/test/parent.js',<br/> '/Users/admin/codes/test/son.js' ]<br/></span>
真相已经呼之欲出了, <span style="font-size: 14px;">Module._cache</span>
里真的出现了两个 <span style="font-size: 14px;">[p|P]arent</span>
( <span style="font-size: 14px;">macOS</span>
默认不区分大小写,所以她找到的其实是同一个文件;但 <span style="font-size: 14px;">node.js</span>
当真了,一看文件名不一样,就当成不同模块了),所以最后问题的关键就在于 <span style="font-size: 14px;">son.js</span>
里到底引用时用了哪个名字(上面我们用了首字母大写的 <span style="font-size: 14px;">require('./Parent.js')</span>
),这才导致了 <span style="font-size: 14px;">test.js</span>
认贼作父的梗。
如果我们改改 <span style="font-size: 14px;">son.js</span>
,把引用换成 <span style="font-size: 14px;">require('./parEND.js')</span>
,再次执行下 <span style="font-size: 14px;">test.js</span>
看看结果如何呢?
<span style="font-size: 14px;">false<br/>false<br/>[ '/Users/haozuo/codes/test/index.js',<br/> '/Users/haozuo/codes/test/Parent.js',<br/> '/Users/haozuo/codes/test/parent.js',<br/> '/Users/haozuo/codes/test/son.js',<br/> '/Users/haozuo/codes/test/parENT.js' ]<br/></span>
没有认贼作父了对不对?再看 <span style="font-size: 14px;">Module._cache</span>
里,原来是 <span style="font-size: 14px;">parENT.js</span>
也被当成一个单独的模块了。
所以,假設你的模組檔名有<span style="font-size: 14px;">n</span>
# 字符,理論上,在<span style="font-size: 14px;"># macOS</span>
大小寫不敏感的檔案系統裡,你能讓<span style="font-size: 14px;">#node.js</span>
將其弄出最大<span style="font-size: 14px;">2</span>
的<span style="font-size: 14px;">n</span>
次方個快取來
#是不是很慘! ?還好 <span style="font-size: 14px;">macOS</span>
還是可以改成大小寫敏感的,格盤重裝系統;新分割區都行。
問題雖然不難,但探究問題的決心和想法還是重要的。
相關推薦:
以上是關於node.js和macOS之間的故事的詳細內容。更多資訊請關注PHP中文網其他相關文章!