>웹 프론트엔드 >JS 튜토리얼 >node.js와 macOS 사이의 이야기

node.js와 macOS 사이의 이야기

小云云
小云云원래의
2017-12-18 11:22:141363검색

이 기사는 node.js와 macOS 사이의 이야기를 여러분과 공유하기 위해 짧은 이야기를 기반으로 작성되었습니다. 모든 사람에게 도움이 되기를 바랍니다.

George G는 컴퓨터로 약간의 테스트를 했는데 결과는 예상과 많이 달랐습니다.

먼저 이 작은 테스트에 작성된 내용을 살펴보겠습니다.

총 3개의 파일, 전체 코드는 15줄을 초과하지 않습니다

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

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

son.js

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

학생 George G가 다음 질문을 가지고 있습니다:

  1. <code><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)之法和中医看病也有相似指出,望、闻、问、切四招可以按需选取一二来寻求答案。

代码不多,看了一会,即便没有我的注释,相信仔细的同学也都发现真正的文件名和代码中引入时有出入的,那么这里肯定是有问题的,问题记住,我们继续

这个就算了,代码我也闻不出个什么鬼来

来吧,软件工程里很重要的一环,就是沟通,不见得是和遇到bug的同事,可能是自己,可能是QA,当然也可能是PM或者你的老板。你没问出自己想知道的问题;他没说清楚自己要回答的;都完蛋。。。。

那么我想知道什么呢?下面两件事作为debug的入口比较合理:

  1. 操作系统

  2. 运行环境 + 版本

  3. 你怎么测试的,命令行还是其他什么手段

答曰:macOS; <span style="font-size: 14px;">node.js > 8.0</span> ;命令行 <span style="font-size: 14px;">node test.js</span>

激动人心的深刻到来了,我要动手了。(为了完整的描述 <span style="font-size: 14px;">debug</span> 过程,我会假装这下面的所有事情我事先都是不知道的)

准备电脑,完毕

准备运行环境 <span style="font-size: 14px;">node.js > 9.3.0</span> , 完毕

复刻代码,完毕

运行,日了狗,果然没报错,而且运行结果就是乔治G说的那样。

为了证明我没瞎,我又尝试在 <span style="font-size: 14px;">test.js</span><span style="font-size: 14px;">require</span> 了一个压根不存在的文件 <span style="font-size: 14px;">require('./nidayede')</span> ,运行代码。

还好这次报错了 <span style="font-size: 14px;">Error: Cannot find module './nidayede'</span>son.js

<p>test. js<span style="font-size: 14px;"></span></p> 잘못된 파일 이름 참조(대소문자 문제)가 있는데 왜 오류가 보고되지 않나요? 🎜🎜🎜🎜테스트 결과, 왜 🎜🎜ss 인스턴스of ParentIncorlect === true🎜🎜인가요? 오류를 보고하지 않는 것은 용납했지만 왜 여전히 내가 잘못된 이름으로 로드된 모듈의 인스턴스라고 생각했습니까? 🎜🎜🎜🎜동급생 여러분이 위의 문제를 이미 명확하게 이해했다면 축하합니다. 펜을 써서 하늘을 진정시킬 수 있고, 무술가가 말을 타고 세상은 강에 있으면 여자를 알 수 있고, 강에 있으면 신발도 알 수 있습니다! 🎜🎜🎜🎜그런데 왜 그런지 모르겠다면? 좋습니다. 그러면 저 중 일부는 이렇게 말할 것이고 여러분 중 일부는 이를 살펴보게 될 것입니다. 🎜🎜🎜🎜사실 진단 방법(가식적으로 디버깅)은 한의학의 방법과 비슷합니다. 보고, 냄새 맡고, 묻고, 촉진하는 네 가지 방법 중 하나 또는 두 가지를 선택하여 답을 찾을 수 있습니다. 필요에 따라. 🎜🎜

🎜Look🎜

🎜🎜코드가 많지 않습니다. 잠시 동안 읽어 본 후에는 제 코멘트가 없더라도 주의 깊은 학생들은 실제 파일 이름과 파일 이름 사이에 불일치가 있음을 알게 될 것이라고 믿습니다. 코드에 소개가 있으니 여기에 문제가 있을 텐데 문제를 기억하고 계속하자🎜🎜

🎜Smell🎜

🎜🎜잊어버려, 코드에서는 아무 냄새도 안 나거든🎜🎜

🎜Ask🎜 h2>🎜🎜자, 소프트웨어 엔지니어링에서 매우 중요한 부분은 의사소통입니다. 버그를 마주한 동료와의 대화는 반드시 본인일 수도 있고, QA일 수도 있고, 물론 PM일 수도 있습니다. 아니면 당신의 상사. 당신은 알고 싶은 질문을 하지 않았습니다. 그는 대답하고 싶은 것이 무엇인지 명확하게 밝히지 않았습니다. . . . 🎜🎜🎜🎜그럼 내가 알고 싶은 것은 무엇인가요? 디버깅의 시작으로 다음 두 가지가 더 합리적입니다. 🎜🎜🎜🎜🎜🎜운영 체제🎜🎜🎜🎜🎜실행 환경 + 버전🎜🎜🎜🎜🎜테스트 방법, 명령줄 또는 기타 의미🎜🎜🎜🎜답변: macOS 🎜🎜node.js > 8.0🎜🎜; 🎜

🎜cut🎜

🎜🎜흥미롭고 심오한 순간이 다가왔습니다. (🎜🎜debug🎜🎜 과정을 완벽하게 설명하기 위해 아래 내용은 미리 모르는 척 할게요) 🎜🎜🎜🎜컴퓨터 준비, 완료🎜🎜🎜🎜 실행 환경 준비 🎜🎜node.js > 9.3.0🎜🎜 , 완료 🎜🎜🎜🎜코드 복사 완료 🎜🎜🎜🎜 실행 중입니다. 놀랍습니다. 오류가 보고되지 않았으며 실행 중입니다. 결과는 George G 입니다. 🎜🎜🎜🎜내가 시각 장애인이 아니라는 것을 증명하기 위해 🎜🎜test.js🎜🎜🎜🎜require🎜에 전혀 존재하지 않는 파일을 추가하려고 했습니다. 🎜 code>🎜require('./nidayede')🎜🎜 , 코드를 실행하세요. 🎜🎜🎜🎜다행히 이번에 오류가 보고되었습니다. 🎜🎜오류: './nidayede' 모듈을 찾을 수 없습니다🎜🎜, 미친 건 아닙니다. 그것은 정말 기쁨입니다. 🎜🎜🎜🎜첫 번째 질문이 나옵니다🎜🎜

고우리의 모듈 이름이 대소문자가 틀린데도 계속 로드되는 이유는 무엇인가요?

운영체제와 관련이 있나요? <code><span style="font-size: 14px;">windows</span> 试试,果然,到了 <code><span style="font-size: 14px;">windows</span> 上,大小写问题就是个问题了, <span style="font-size: 14px;">Error: Cannot find module './Parent'</span>

那么 <span style="font-size: 14px;">macOS</span> 到底在干什么?连个大小写都分不出来么?于是赶紧 <span style="font-size: 14px;">google</span> (别问我为什么不baidu)

node.js와 macOS 사이의 이야기

原来人家牛逼的 <span style="font-size: 14px;">OS X</span> 默认用了 <span style="font-size: 14px;">case-insensitive</span> 的文件系统( 详细文档 )。

but why?这么反人类的设计到底是为了什么?

node.js와 macOS 사이의 이야기

更多解释, 来,走你

所以,这就是你不报错的理由?(对 <span style="font-size: 14px;">node.js</span> 指责道),但这就是全部真相了。

但事情没完

那认贼作父又是个什么鬼?

依稀有听过 <span style="font-size: 14px;">node.js</span> 里有什么缓存,是那个东西引起的么?于是抱着试试看的心情,我把 <span style="font-size: 14px;">const ParentIncorrect = require('./Parent')</span><span style="font-size: 14px;">const Parent = require('./parent')</span> 换了下位置,心想,这样最先按照正确的名字加载,会不会就对了呢?

果然, 还是不对 。靠猜和装逼是不能够真正解决问题的

那比比 <span style="font-size: 14px;">ParentIncorrect</span><span style="font-size: 14px;">Parent</span> 呢?于是我写了 <span style="font-size: 14px;">console.log(ParentIncorrect === Parent)</span> ,结果为 <span style="font-size: 14px;">false</span> 。所以他俩还真的不是同一个东西,那么说明问题可能在引入的部分喽?

于是一个装逼看 <span style="font-size: 14px;">node.js</span> 源码的想法诞生了(其实不看,问题最终也能想明白)。 日了狗,怀着忐忑的心情,终于 <span style="font-size: 14px;">clone</span> 了一把 <span style="font-size: 14px;">node.js</span> 源码(花了好久,真tm慢)

来,我们一起进入神秘的 <span style="font-size: 14px;">node.js</span> 源码世界。既然我们的问题是有关 <span style="font-size: 14px;">require</span> 的,那就从她开始吧,不过找到 <span style="font-size: 14px;">require</span> 定义的过程需要点耐心,这里不详述,只说查找的顺序吧

<span style="font-size: 14px;">src/node_main.cc => src/node.cc => lib/internal/bootstrap_node.js => lib/module.js</span>

找到咯,就是这个 <span style="font-size: 14px;">lib/module.js</span>windows에서 다시 시도해 보겠습니다. 물론입니다.

<p>windows<span style="font-size: 14px;"></span></p>에서 케이스 문제가 문제입니다. 🎜 오류: 불가능 모듈 './Parent'🎜🎜를 찾으세요. 🎜🎜🎜🎜그럼 🎜🎜macOS🎜🎜는 무엇을 하고 있나요? 대문자와 소문자의 차이를 구분할 수 없나요? 너무 빨리 🎜🎜google🎜🎜 (baidu가 아닌 이유는 묻지 마세요)🎜🎜🎜node.js와 macOS 사이의 이야기 🎜🎜🎜멋진 🎜🎜OS X🎜🎜은 기본적으로 🎜🎜대소문자를 구분하지 않는🎜🎜 파일 시스템을 사용하는 것으로 나타났습니다(자세한 문서). 🎜🎜🎜🎜근데 왜? 그러한 반인간적인 디자인의 목적은 무엇입니까? 🎜🎜🎜node.js와 macOS 사이의 이야기 🎜🎜🎜자세한 설명은 오고 가세요🎜🎜🎜🎜그래서 이것이 바로 여러분이 하지 않는 이유입니다. 오류 보고 이유는? (🎜🎜node.js🎜🎜을 비난) 하지만 그게 전부 진실입니다. 🎜🎜🎜🎜아직 끝나지 않았습니다 🎜🎜

🎜그럼 도둑이 아버지라니 대체 뭐죠? 🎜

🎜🎜어렴풋이 들어봤는데 🎜🎜node.js🎜🎜에 캐시가 있는 걸까요? 그래서 시도해 보고 싶은 마음에 🎜🎜const ParentIncorlect = require('./Parent')🎜🎜 및 🎜🎜const Parent = require('./parent')를 입력했습니다. )🎜 🎜 위치를 바꿔서 먼저 올바른 이름으로 로드하는 것이 맞을까? 🎜🎜🎜🎜물론이죠. 아직도 정확하지 않습니다. 추측하고 가장하는 것만으로는 문제를 해결할 수 없습니다🎜🎜🎜🎜Bibi 🎜🎜ParentIncorlect🎜🎜와 🎜🎜Parent🎜🎜는 어떻습니까? 그래서 🎜🎜console.log(ParentIncordirect === Parent)🎜🎜 라고 썼는데, 결과는 🎜🎜false🎜🎜 였습니다. 그러면 실제로는 같은 것이 아니므로 도입 부분에 문제가 있는 것이 아닐까요? 🎜🎜🎜🎜그래서 🎜🎜node.js🎜🎜의 소스코드를 살펴보자는 아이디어가 탄생했습니다(사실 보지 않아도 문제는 결국 알 수 있습니다). 긴 하루를 보낸 후, 떨리는 마음으로 마침내 🎜🎜clone🎜🎜 소수의 🎜🎜node.js🎜🎜 소스 코드를 얻었습니다(오랜 시간이 걸렸습니다). 시간이 너무 느려) 🎜🎜🎜🎜자, 신비한 🎜🎜node.js🎜🎜 소스코드 세계로 함께 들어가보자. 우리의 질문은 🎜🎜require🎜🎜에 관한 것이므로 그녀부터 시작하겠습니다. 하지만 🎜🎜require🎜🎜의 정의를 찾는 과정에는 약간의 인내심이 필요합니다. 자세한 내용은 검색 순서에 대해서만 이야기하겠습니다🎜src/node_main.cc => src/node.cc => lib/internal/bootstrap_node.js => 🎜 code> 🎜🎜🎜 찾았습니다. 바로 이것이 🎜🎜lib/module.js🎜🎜입니다. 요점을 살펴보겠습니다. 🎜🎜🎜🎜lib/module.js => 🎜
<span   style="max-width:90%">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. <code><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> 当真了,一看文件名不一样,就当成不同模块了),所以最后问题的关键就在于 <code><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> 认贼作父的梗。

如果我们改改 <code><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> 也被当成一个单独的模块了。

따라서 모듈 파일 이름에 이론적으로 <code><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><code><span style="font-size: 14px;">n</span> 次方个缓存来

是不是很惨!?还好 <span style="font-size: 14px;">macOS</span>macOS 대소문자를 구분하지 않는 파일 시스템에서

<p>n<span style="font-size: 14px;"></span></p> 문자가 있다고 가정하면 <p>node.js<span style="font-size: 14px;"></span></p> 최대의 <p>2<a href="http://www.php.cn/js-tutorial-382351.html" target="_self" style="font-size: 14px; text-decoration: underline;"></a></p>n

전력으로 만드는 것은 비참하지 않나요? 캐시를 가지고 ! ? 다행히

<p>macOS<a href="http://www.php.cn/js-tutorial-379690.html" target="_self" style="font-size: 14px; text-decoration: underline;"></a></p>는 여전히 대소문자 구분으로 변경될 수 있으며, 그리드 디스크에 시스템을 다시 설치하거나 새 파티션을 생성하면 됩니다. 🎜문제가 어렵지는 않지만 문제를 탐구하려는 결단력과 아이디어는 여전히 중요합니다. 🎜🎜🎜🎜관련 권장사항: 🎜🎜🎜🎜🎜node.js를 사용하여 하위 프로세스를 만드는 방법을 가르쳐주세요🎜🎜🎜🎜🎜🎜PHP 및 Node.js🎜🎜🎜🎜🎜🎜node.js 게시-구독 모드 방법 🎜🎜🎜

위 내용은 node.js와 macOS 사이의 이야기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.