首頁 >web前端 >js教程 >關於node.js和macOS之間的故事

關於node.js和macOS之間的故事

小云云
小云云原創
2017-12-18 11:22:141363瀏覽

本文由一個小故事來和大家分享關於node.js和macOS之間的故事,希望能幫助大家。

喬治G在他的電腦上做了一個小測試,但結果和預期的大不相同。

那麼我們先來看看這個小測試都寫了什麼:

總共三個文件,程式碼總計不超過15行

<span style="font-size: 14px;">parent.js</span>

<span style="font-size: 14px;">class Parent {}<br><br>module.exports = Parent<br></span>

<span style="font-size: 14px;">son.js</span>

<span style="font-size: 14px;">//加载时把模块文件名首字母大写了(不正确的)<br/>const Parent = require(&#39;./Parent&#39;)<br/><br/>class Son extends Parent {}<br/><br/>module.exports = Son<br/></span>

<span style="font-size: 14px;">test.js</span>

<span style="font-size: 14px;">//加载时把模块名首字母大写(不正确的)<br/>const ParentIncorrect = require(&#39;./Parent&#39;)<br/>//通过正确的模块文件名加载(正确)<br/>const Parent = require(&#39;./parent&#39;)<br/><br/>const Son = require(&#39;./son&#39;)<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同學有以下問題:

  1. <span style="font-size: 14px;">son.js</span><span style="font-size: 14px;">test.js</span> 裡都有錯誤的檔名(大小寫問題)引用,為什麼不報錯?

  2. 測試結果,為什麼 <span style="font-size: 14px;">ss instanceof ParentIncorrect === true</span> ?不報錯我忍了,為什麼還認賊作父,說自己是那個透過不正確名字載入出來的模組的instance?

如果同學你對上述問題已經了然於胸,恭喜你,文能提筆安天,武能上馬定乾坤;上炕認識娘們,下炕認識鞋!

但如果你也不是很清楚為什麼?那麼好了,我有的說,你有的看。

其實斷症(裝逼範兒的debug)之法和中醫看病也有相似指出,望、聞、問、切四招可以按需選取一二來尋求答案。

程式碼不多,看了一會兒,即使沒有我的註釋,相信仔細的同學也都發現真正的文件名和程式碼中引入時有出入的,那麼這裡肯定是有問題的,問題記住,我們繼續

<span style="font-size: 14px;"></span>

#這個就算了,程式碼我也聞不出個什麼鬼來

#來吧,軟體工程裡很重要的一環,就是溝通,不見得是和遇到bug的同事,可能是自己,可能是QA,當然也可能是PM或是你的老闆。你沒問出自己想知道的問題;他沒說清楚自己要回答的;都完蛋。 。 。 。

那我想知道什麼呢?以下兩件事作為debug的入口比較合理:

  1. 作業系統

  2. ##運行環境+ 版本<span style="font-size: 14px;"></span>

  3. 你怎麼測試的,命令列還是其他什麼手段<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>

令人興奮的深刻到來了,我要動手了。 (為了完整的描述

<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>

關於node.js和macOS之間的故事

##原來人家牛逼的

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>

but why?這麼反人類的設計到底是為了什麼?

<span style="font-size: 14px;"></span>

關於node.js和macOS之間的故事

更多解釋, 來,走你

所以,這就是你不報錯的理由? (對

<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>

依稀有聽過

<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> 的,那就從她開始吧,不過找到

##require

<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, &#39;missing path&#39;);<br/>  assert(typeof path === &#39;string&#39;, &#39;path must be a string&#39;);<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(&#39;Module._load REQUEST %s parent: %s&#39;, 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(&#39;load native module %s&#39;, 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 = &#39;.&#39;;<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> 了,要不我们先停一停?

好了,分析问题的关键在于不忘初心,虽然到目前为止我们前进的比较顺利,也很爽对不对?。但我们的此行的目的并不是爽,好像是有个什么疑惑哦!于是,我们再次梳理下问题:

  1. <span style="font-size: 14px;">son.js</span> 里用首字母大写(不正确)的模块名引用了 <span style="font-size: 14px;">parent.js</span>

  2. <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/>[ &#39;/Users/admin/codes/test/index.js&#39;,<br/>  &#39;/Users/admin/codes/test/Parent.js&#39;,<br/>  &#39;/Users/admin/codes/test/parent.js&#39;,<br/>  &#39;/Users/admin/codes/test/son.js&#39; ]<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/>[ &#39;/Users/haozuo/codes/test/index.js&#39;,<br/>  &#39;/Users/haozuo/codes/test/Parent.js&#39;,<br/>  &#39;/Users/haozuo/codes/test/parent.js&#39;,<br/>  &#39;/Users/haozuo/codes/test/son.js&#39;,<br/>  &#39;/Users/haozuo/codes/test/parENT.js&#39; ]<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建立子程式

#PHP與Node.js

#node.js 發布訂閱模式的方法

以上是關於node.js和macOS之間的故事的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn