이 글에서는 Node의 파일 모듈과 핵심 모듈을 살펴보고 파일 모듈 검색, 파일 모듈 컴파일 및 실행, JavaScript 및 C/C++ 핵심 모듈 I의 컴파일 및 실행에 대해 설명합니다. 모두에게 도움이 되길 바랍니다!
일상적인 개발을 위해 Nodejs를 사용할 때 종종 두 가지 유형의 모듈을 가져오기 위해 require를 사용합니다. 하나는 우리가 직접 작성한 모듈이거나 npm을 사용하여 설치된 타사 모듈입니다. Node.파일 모듈
; 다른 유형은 os
, fs
및 기타 모듈과 같이 우리가 사용할 수 있는 Node의 내장 모듈입니다. 모듈을 코어 모듈
이라고 합니다. 文件模块
;另一类则是 Node 内置的提供给我们使用的模块,如 os
、fs
等模块,这些模块被称为 核心模块
。
需要注意的是,文件模块与核心模块的差异不仅仅在于是否被 Node 内置,具体到模块的文件定位、编译和执行过程,两者之间都存在明显的差别。不仅如此,文件模块还可以被细分为普通文件模块、自定义模块或 C/C++ 扩展模块等等,不同的模块在文件定位、编译等流程也存在诸多细节上的不同。
本文会就这些问题,理清文件模块与核心模块的概念以及它们在文件定位、编译或执行等流程的具体过程和需要注意的细节,希望对你有所帮助。
我们先从文件模块讲起。
什么是文件模块呢?
在 Node 中,使用 .、.. 或 /
开头的模块标识符(也就是使用相对路径或绝对路径)来 require 的模块,都会被当作文件模块。另外,还有一类特殊的模块,虽然不含有相对路径或绝对路径,也不是核心模块,但是会指向一个包,Node 在定位这类模块时,会用 模块路径
逐个查找该模块,这类模块被称为自定义模块。
因此,文件模块包含两类,一类是带路径的普通文件模块,一类是不带路径的自定义模块。
文件模块在运行时动态加载,需要完整的文件定位、编译执行过程,速度比核心模块慢。
对于文件定位而言,Node 对这两类文件模块的处理有所不同。我们来具体看看这两类文件模块的查找流程。
对于普通的文件模块,由于携带路径,指向非常明确,查找耗时不会很久,因此查找效率比下文要介绍的自定义模块要高一些。不过还是有两点需要注意。
一是通常情况下,使用 require 引入文件模块时一般都不会指定文件扩展名,比如:
const math = require("math");
由于没有指定扩展名,Node 还不能确定最终的文件。在这种情况下,Node 会按 .js、.json、.node
的顺序补足扩展名,依次尝试,这个过程被称为 文件扩展名分析
。
另外需要注意的是,在实际开发中,除了 require 一个具体的文件外,我们通常还会指定一个目录,比如:
const axios = require("../network");
在这种情况下,Node 会先进行文件扩展名分析,如果没有查找到对应文件,但是得到了一个目录,此时 Node 会将该目录当作一个包来处理。
具体而言,Node 会将目录中的 package.json
的 main
字段所指向的文件作为查找结果返回。如果 main 所指向的文件错误,或者压根不存在 package.json
文件,Node 会使用 index
作为默认文件名,然后依次使用 .js
、.node
进行扩展名分析,逐个查找目标文件,如果没有找到的话就会抛出错误。
(当然,由于 Node 存在两类模块系统 CJS 和 ESM,除了查找 main 字段外,Node 还会采用其他方式,由于不在本文讨论范围内,就不再赘述了。)
刚才提到,Node 在查找自定义模块的过程中,会使用到模块路径,那什么是模块路径呢?
熟悉模块解析的朋友应该都知道,模块路径是一个由路径组成的数组,具体的值可以看以下这个示例:
// example.js console.log(module.paths);
打印结果:
可以看到,Node 中的模块存在一个模块路径数组,存放在 module.paths
., .. 또는 /
로 시작하는 모듈 식별자(즉, 상대 경로 또는 절대 경로 사용)를 사용하는 데 필요한 모듈은 파일 모듈로 처리됩니다. 또한, 상대 경로나 절대 경로를 포함하지 않고 핵심 모듈도 아니지만 특별한 유형의 모듈이 있습니다. 노드가 이러한 유형의 모듈을 찾을 때 모듈 경로를 하나씩 검색합니다. 이 모듈, 이러한 유형의 모듈을 사용자 정의 모듈이라고 합니다. 🎜🎜따라서 파일 모듈에는 두 가지 유형이 있습니다. 하나는 경로가 있는 일반 파일 모듈이고 다른 하나는 경로가 없는 사용자 정의 모듈입니다. 🎜🎜파일 모듈은 런타임 시 동적으로 로드되므로 완전한 파일 위치 지정, 컴파일 및 실행 프로세스가 필요하며 핵심 모듈보다 느립니다. 🎜🎜파일 위치 지정의 경우 Node는 이 두 가지 유형의 파일 모듈을 다르게 처리합니다. 이 두 가지 유형의 파일 모듈에 대한 검색 프로세스를 자세히 살펴보겠습니다. 🎜(function(exports, require, module, __filename, __dirname) { // 模块代码 });
.js, .json, .node
순서로 확장자를 완성하고 하나씩 시도하게 됩니다. 이 과정을 파일 확장자 분석
이라고 합니다. . 🎜🎜또 한 가지 주목해야 할 점은 실제 개발에서는 특정 파일을 요구하는 것 외에도 일반적으로 다음과 같은 디렉터리도 지정한다는 것입니다. 🎜rrreee🎜이 경우 Node는 먼저 해당 파일이 다음과 같은 경우 파일 확장자 분석을 수행합니다. 찾을 수 없지만 디렉토리를 얻었습니다. 이때 Node는 디렉토리를 패키지로 처리합니다. 🎜🎜구체적으로 Node는 디렉터리에 있는 package.json
의 main
필드가 가리키는 파일을 검색 결과로 반환합니다. main이 가리키는 파일이 잘못되었거나 package.json
파일이 전혀 존재하지 않는 경우 Node는 index
를 기본 파일 이름으로 사용한 다음 를 사용합니다. >.js code>, <code>.node
는 확장 분석을 수행하고 대상 파일을 하나씩 검색합니다. 🎜🎜 (물론 Node에는 CJS와 ESM 두 종류의 모듈 시스템이 있기 때문에 Node에서는 메인 필드를 찾는 것 외에 다른 방법도 사용하게 됩니다. 이 글의 범위를 벗어나므로 자세히 다루지는 않겠습니다. .) 🎜module.paths
에 저장되는 배열은 Node가 현재 모듈에서 참조하는 사용자 정의 모듈을 찾는 방법을 지정하는 데 사용됩니다. 🎜具体来讲,Node 会遍历模块路径数组,逐个尝试其中的路径,查找该路径对应的 node_modules
目录中是否有指定的自定义模块,如果没有就向上逐级递归,一直到根目录下的 node_modules
目录,直到找到目标模块为止,如果找不到的话就会抛出错误。
可以看出,逐级向上递归查找 node_modules
目录是 Node 查找自定义模块的策略,而模块路径便是这个策略的具体实现。
同时我们也得出一个结论,在查找自定义模块时,层级越深,相应的查找耗时就会越多。因此相比于核心模块和普通的文件模块,自定义模块的加载速度是最慢的。
当然,根据模块路径查找到的仅仅是一个目录,并不是一个具体的文件,在查找到目录后,同样地,Node 会根据上文所描述的包处理流程进行查找,具体过程不再赘述了。
以上是普通文件模块和自定义模块的文件定位的流程和需要注意的细节,接下来我们来看者两类模块是如何编译执行的。
当定位到 require 所指向的文件后,通常模块标识符都不带有扩展名,根据上文提到的文件扩展名分析我们可以知道,Node 支持三种扩展名文件的编译执行:
JavaScript 文件。通过 fs
模块同步读取文件后编译执行。除了 .node
和 .json
文件,其他文件都会被当作 .js
文件载入。
.node
文件,这是用 C/C++ 编写后编译生成的扩展文件,Node 通过 process.dlopen()
方法加载该文件。
json 文件,通过 fs
模块同步读取文件后,使用 JSON.parse()
解析并返回结果。
在对文件模块进行编译执行之前,Node 会使用如下所示的模块封装器对其进行包装:
(function(exports, require, module, __filename, __dirname) { // 模块代码 });
可以看到,通过模块封装器,Node 将模块包装进函数作用域中,与其他作用域隔离,避免变量的命名冲突、污染全局作用域等问题,同时,通过传入 exports、require 参数,使该模块具备应有的导入与导出能力。这便是 Node 对模块的实现。
了解了模块封装器后,我们先来看 json 文件的编译执行流程。
json 文件的编译执行是最简单的。在通过 fs
模块同步读取 JSON 文件的内容后,Node 会使用 JSON.parse() 解析出 JavaScript 对象,然后将它赋给该模块的 exports 对象,最后再返回给引用它的模块,过程十分简单粗暴。
在使用模块包装器对 JavaScript 文件进行包装后,包装之后的代码会通过 vm
模块的 runInThisContext()
(类似 eval) 方法执行,返回一个 function 对象。
然后,将该 JavaScript 模块的 exports、require、module 等参数传递给这个 function 执行,执行之后,模块的 exports 属性被返回给调用方,这就是 JavaScript 文件的编译执行过程。
在讲解 C/C++ 扩展模块的编译执行之前,先介绍一下什么是 C/C++ 扩展模块。
C/C++ 扩展模块属于文件模块中的一类,顾名思义,这类模块由 C/C++ 编写,与 JavaScript 模块的区别在于其加载之后不需要编译,直接执行之后就可以被外部调用了,因此其加载速度比 JavaScript 模块略快。相比于用 JS 编写的文件模块,C/C++ 扩展模块明显更具有性能上的优势。对于 Node 核心模块中无法覆盖的功能或者有特定的性能需求,用户可以编写 C/C++ 扩展模块来达到目的。
那 .node
文件又是什么呢,它跟 C/C++ 扩展模块有什么关系?
事实上,编写好之后的 C/C++ 扩展模块经过编译之后就生成了 .node
文件。也就是说,作为模块的使用者,我们并不直接引入 C/C++ 扩展模块的源代码,而是引入 C/C++ 扩展模块经过编译之后的二进制文件。因此,.node
文件并不需要编译,Node 在查找到 .node
文件后,只需加载和执行该文件即可。在执行的过程中,模块的 exports 对象被填充,然后返回给调用者。
C/C++ 확장 모듈을 컴파일하여 생성된 .node
파일은 플랫폼마다 다른 형태를 갖는다는 점에 주목할 가치가 있습니다. *nix
시스템의 C/C++ 확장 모듈 이는 Windows
에서 .so
확장자를 사용하여 g++/gcc와 같은 컴파일러에 의해 동적 링크 공유 개체 파일로 컴파일되며 동적 링크 라이브러리 파일로 컴파일됩니다. Visual C++ 컴파일러의 경우 확장자는 .dll
입니다. 하지만 실제로 사용하는 확장자는 .node
입니다. 사실 .node
의 확장자는 실제로 에서 더 자연스럽게 보이기 위한 것입니다. >Windows
의 .dll
파일 및 *nix
의 .so
파일. .node
文件在不同平台下有不同的形式:在 *nix
系统下C/C++ 扩展模块被 g++/gcc 等编译器编译为动态链接共享对象文件,扩展名为 .so
;在 Windows
下则被 Visual C++ 编译器编译为动态链接库文件,扩展名为 .dll
。但是在我们实际使用时使用的扩展名却是 .node
,事实上 .node
的扩展名只是为了看起来更自然一点,实际上,在 Windows
下它是一个 .dll
文件,在 *nix
下则是一个 .so
文件。
Node 在查找到要 require 的 .node
文件之后,会调用 process.dlopen()
方法对该文件进行加载和执行。由于 .node
文件在不同平台下是不同的文件形式,为了实现跨平台,dlopen()
方法在 Windows
和 *nix
平台下分别有不同的实现,然后通过 libuv
兼容层进行封装。下图是 C/C++ 扩展模块在不同平台下编译和加载的过程:
核心模块在 Node 源代码的编译过程中,就编译进了二进制执行文件。在 Node 进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中会比文件模块优先判断,所以它的加载速度是最快的。
核心模块其实分为 C/C++ 编写的和 JavaScript 编写的两部分,其中 C/C++ 文件存放在 Node 项目的 src 目录下,JavaScript 文件存放在 lib 目录下。显然,这两部分模块的编译执行流程都有所不同。
对于 JavaScript 核心模块的编译,在 Node 源代码的编译过程中,Node 会采用 V8 附带的 js2c.py 工具,将所有内置的 JavaScript 代码,包括 JavaScript 核心模块,转换为 C++ 里的数组,JavaScript 代码就这样以字符串的形式存储在 node 命名空间中。在启动 Node 进程时,JavaScript 代码就会直接加载进内存。
当引入 JavaScript 核心模块时,Node 会调用 process.binding()
通过模块标识符分析定位到其在内存中的位置,将其取出。在取出后,JavaScript 核心模块同样会经历模块包装器的包装,然后被执行,导出 exports 对象,返回给调用者。
在核心模块中,有些模块全部由 C/C++ 编写,有些模块则由 C/C++ 完成核心部分,其他部分则由 JavaScript 实现包装或向外导出,以满足性能需求,像 buffer
、fs
、os
等模块都是部分通过 C/C++ 编写的。这种 C++ 模块主内完成核心,JavaScript 模块主外实现封装的模式是 Node 提高性能的常见方式。
核心模块中由纯 C/C++ 编写的部分称为内建模块,如 node_fs
、node_os
等,它们通常不被用户直接调用,而是被 JavaScript 核心模块直接依赖。因此,在 Node 的核心模块的引入过程中,存在这样一条引用链:
那 JavaScript 核心模块是如何加载内建模块的呢?
还记得 process.binding()
方法吗,Node 通过调用该方法实现将 JavaScript 核心模块从内存中取出。该方法同样适用于 JavaScript 核心模块,来协助加载内建模块。
具体到该方法的实现,加载内建模块时,首先创建一个 exports 空对象,然后调用 get_builtin_module()
方法取出内建模块对象,通过执行 register_func()
.node
파일을 찾은 후 process.dlopen()
메서드를 호출하여 파일을 로드하고 실행합니다. .node
파일은 플랫폼마다 파일 형식이 다르기 때문에 크로스 플랫폼을 구현하기 위해 Windows
에서는 dlopen()
메서드를 사용합니다. 및 *nix
플랫폼에는 다양한 구현이 있으며 libuv
호환성 계층을 통해 캡슐화됩니다. 아래 그림은 다양한 플랫폼에서 C/C++ 확장 모듈의 컴파일 및 로딩 프로세스를 보여줍니다.
핵심 모듈은 실제로 C/C++와 JavaScript로 작성된 두 부분으로 나누어져 있습니다. C/C++ 파일은 Node 프로젝트의 src 디렉터리에 저장되고, JavaScript 파일은 lib 디렉터리에 저장됩니다. 분명히 이 두 모듈의 컴파일 및 실행 프로세스는 다릅니다.
process.bound()
를 호출하여 모듈 식별자 분석을 통해 메모리에서 해당 위치를 찾고 검색합니다. 꺼낸 후 JavaScript 핵심 모듈도 모듈 래퍼로 래핑된 다음 실행되고 내보내기 개체가 내보내지고 호출자에게 반환됩니다. 🎜buffer
, fs
, 와 같은 성능 요구 사항을 충족하기 위해 다른 부분은 JavaScript로 패키징되거나 내보내집니다. os
code> 및 기타 모듈은 부분적으로 C/C++로 작성되었습니다. 본체 내부에서 코어를 완성하는 C++ 모듈과 본체 외부에서 캡슐화를 구현하는 JavaScript 모듈의 이러한 모드는 Node가 성능을 향상시키는 일반적인 방법입니다. 🎜🎜순수 C/C++로 작성된 핵심 모듈 부분을 node_fs
, node_os
등과 같은 내장 모듈이라고 합니다. 일반적으로 직접 호출하지 않습니다. 사용자이지만 JavaScript 핵심 모듈은 사용자에게 직접 의존합니다. 따라서 Node의 핵심 모듈 도입 과정에는 다음과 같은 참조 체인이 있습니다. 🎜🎜process.bound()
메서드를 기억하시나요? Node는 이 메서드를 호출하여 JavaScript 핵심 모듈을 메모리에서 꺼냅니다. 이 방법은 JavaScript 핵심 모듈과 함께 작동하여 내장 모듈 로드를 지원합니다. 🎜🎜이 메소드 구현에 따라 내장 모듈을 로드할 때 먼저 내보내기 빈 객체를 생성한 다음 get_builtin_module()
메소드를 호출하여 내장 모듈 객체를 검색하고 실행합니다. register_func()는 내보내기 개체를 채우고 마지막으로 이를 호출자에게 반환하여 내보내기를 완료합니다. 내장 모듈을 로딩하고 실행하는 과정입니다. 🎜🎜위의 분석을 통해 코어 모듈과 같은 참조 체인 도입을 위해 os 모듈을 예로 들면 일반적인 프로세스는 다음과 같습니다. 🎜🎜🎜🎜🎜요약하면 os 모듈을 도입하는 과정은 다음과 같습니다. 자바스크립트 파일 모듈과 자바스크립트 코어 모듈에 대한 소개를 진행합니다. 내장 모듈의 로딩과 실행, 내장 모듈의 로딩과 실행은 모듈을 호출하는 사람 입장에서는 매우 번거롭고 복잡합니다. 기본 복잡한 구현 및 세부 사항이 보호되므로 전체 모듈 가져오기는 require()를 통해서만 완료될 수 있으며 이는 매우 간결합니다. 친숙한. 🎜<h2 data-id="heading-10"><strong>요약</strong></h2>
<p>이 글에서는 파일 모듈과 핵심 모듈의 기본 개념을 소개하고, 파일 위치, 컴파일, 실행 시 주의해야 할 구체적인 프로세스와 세부 사항을 소개합니다. 구체적으로: </p>
<ul>
<li><p>파일 모듈은 다양한 파일 위치 지정 프로세스에 따라 일반 파일 모듈과 사용자 정의 모듈로 나눌 수 있습니다. 일반 파일 모듈은 명확한 경로로 인해 직접 찾을 수 있으며 때로는 파일 확장자 분석 및 디렉터리 분석 프로세스가 포함됩니다. 사용자 정의 모듈은 모듈 경로를 기반으로 검색하고 성공적인 검색 후 디렉터리 분석을 통해 최종 파일 위치가 수행됩니다. . </p></li>
<li><p>파일 모듈은 다양한 컴파일 및 실행 프로세스에 따라 JavaScript 모듈과 C/C++ 확장 모듈로 나눌 수 있습니다. JavaScript 모듈은 모듈 래퍼에 의해 패키징되고 <code>vm
모듈의 runInThisContext
메서드를 통해 실행됩니다. C/C++ 확장 모듈은 이미 컴파일 후에 생성된 실행 파일입니다. 직접 실행하고 내보낸 개체를 호출자에게 반환할 수 있습니다. vm
模块的 runInThisContext
方法进行执行;C/C++ 扩展模块由于已经是经过编译之后生成的可执行文件,因此可直接执行,返回导出对象给调用方。
核心模块分为 JavaScript 核心模块和内建模块。JavaScript 核心模块在 Node 进程启动时便被加载进内存中,通过 process.binding()
方法可将其取出,然后执行;内建模块的编译执行会经历 process.binding()
、get_builtin_module()
和 register_func()
process.bind()
메서드를 통해 실행될 수 있습니다. process.handling()
, get_builtin_module()
및 register_func()
함수를 살펴보세요. 또한 핵심 모듈, 즉 파일 모듈-->JavaScript 핵심 모듈-->내장 모듈을 소개하기 위한 Node용 참조 체인도 얻었고
C++ 모듈도 배웠습니다. 메인 모듈 내의 핵심을 완성하기 위해 JavaScript 모듈은 캡슐화라는 모듈 작성 방법을 구현합니다. 더 많은 프로그래밍 관련 지식을 보려면
프로그래밍 비디오🎜를 방문하세요! ! 🎜위 내용은 한 기사에서 Node의 파일 모듈과 핵심 모듈에 대해 알아보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!