Heim  >  Artikel  >  Web-Frontend  >  Was ist die CommonJS-Modulspezifikation? Eine kurze Analyse des Modulmechanismus von Nodejs

Was ist die CommonJS-Modulspezifikation? Eine kurze Analyse des Modulmechanismus von Nodejs

青灯夜游
青灯夜游nach vorne
2021-08-16 10:12:291731Durchsuche

Was ist die CommonJS-Modulspezifikation? Eine kurze Analyse des Modulmechanismus von Nodejs

Node-Anwendung besteht aus Modulen. Ihr Modulsystem basiert auf der Modulspezifikation CommonJS, ist jedoch nicht vollständig gemäß der Spezifikation implementiert, sondern fügt einige hinzu Funktionen entsprechend den eigenen Anforderungen, gilt als Variante der CommonJS-Modulspezifikation. Node应用由模块组成,其模块系统借鉴了CommonJS模块规范,但是并未完全按照规范实现,而是根据自身需求增加了一些特性,算是CommonJS模块规范的一个变种。

CommonJS概述

CommonJS是社区提出的一种JavaScript模块化规范,可以说是JS模块化历程中最重要的一块里程碑,它构造了一个美好的愿景——JS能够在任何地方运行,但其实由于它的模块是同步加载的,只适合在服务端等其他本地环境,并不适合浏览器端等需要异步加载资源的地方。

为了能让JS能够在任何地方运行,CommonJS制定了一些接口规范,这些接口覆盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、socket、单元测试、web服务器、网关、包管理等等,虽然大部分都处于草案阶段,但是其深深影响了Node的发展。

下图表示了Node与浏览器、W3CCommonJS以及ECMAScript之间的关系,摘自 《深入浅出NodeJS》

Was ist die CommonJS-Modulspezifikation? Eine kurze Analyse des Modulmechanismus von Nodejs

CommonJS的模块规范

CommonJS的模块主要由模块引用模块定义模块标识三部分组成。

模块标识

模块标识对于每个模块来说是唯一的,是它被引用时的依据,它必须是符合小驼峰命名的字符串,或者是文件的相对路径或绝对路径。

require('fs')// fs是内建模块,执行时会被直接载入内存,无须路径标识
require('./moduleA')//导入当前目录的moduleA
require('../moduleB')// 导入上一个目录的moduleB
require('C://moduleC')// 绝对路径导入moduleC

模块引用

使用require()来引用一个模块,这个方法接受一个模块标识作为参数,以此引入一个模块的API到当前上下文中。

const fs = require('fs')// 引入内建的fs模块

模块定义

有导入自然也有导出,要将当前上下文中的方法或变量作为模块导出,需要使用内建的module.exports对象,它是模块导出的唯一出口。

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

// moduleA.js模块
let moduleA = {
    name:"moduleA"
}
module.exports = {
    moduleA
}

// moduleB.js模块
// 导入moduleA
const {moduleA} = require('./moduleA')

CommonJS模块的特点如下:

  • 每个模块具有独立的上下文,模块内的代码独立执行,不会污染全局作用域。
  • 模块可以被多次加载,但是只会在第一次加载时运行,运行结果会被缓存,后续再加载相同模块会直接读取缓存结果,缓存存储在module.cache
  • 模块的加载按代码顺序执行。

Node的模块实现

Node导入模块需要经历3个步骤:路径分析 -> 文件定位 -> 编译执行:

  • 路径分析:根据模块标识分析模块类型。

  • 文件定位:根据模块类型和模块标识符找到模块所处位置。

  • 编译执行:将文件编译成机器码执行,中间需要经过一系列转化。

【推荐学习:《nodejs 教程》】

模块类型分为内建模块和用户模块:

  • 内建模块:内建模块由Node提供,已经被编译成二进制执行文件,在node执行时,内建模块会被直接载入内存,因此我们可以直接引入,它的加载速度很快,因为它不需要经过文件定位和编译执行这2个步骤。

  • 文件模块:使用jsC++等编写的扩展模块,执行时需要先被编译成二进制机器码。需要经过上述三大步骤。

模块缓存

不管是内建模块还是文件模块,node

CommonJS-Übersicht

CommonJS ist ein von der Community vorgeschlagenes JavaScript-Modul Man kann sagen, dass die Standardisierung der wichtigste Meilenstein im Modularisierungsprozess von JS ist. Sie schafft eine schöne Vision – JS kann überall ausgeführt werden, aber tatsächlich, weil seine Module geladen sind synchron und sind nur für andere lokale Umgebungen wie die Serverseite geeignet. Sie sind nicht für Browser und andere Orte geeignet, die Ressourcen asynchron laden müssen. 🎜🎜Damit JS überall ausgeführt werden kann, hat CommonJS einige Schnittstellenspezifikationen formuliert, die Module, Binärdateien, Puffer und Zeichensatzkodierung abdecken , I/O-Streams, Prozessumgebung, Dateisystem, Socket, Unit-Tests, Webserver, Gateway, Paketverwaltung usw., obwohl sich die meisten davon im Entwurfsstadium befinden , aber es hat die Entwicklung von Node tiefgreifend beeinflusst. 🎜🎜Die folgende Abbildung zeigt die Beziehung zwischen Node und dem Browser, W3C, CommonJS und ECMAScript, auszugsweise „Ausführliche Erklärung von NodeJS“🎜🎜1 .png🎜

CommonJS-Modulspezifikation

🎜Das Modul von CommonJS wird hauptsächlich von Modul, Moduldefinition und Modulidentität. 🎜🎜Modul-ID🎜🎜Die Modul-ID ist für jedes Modul eindeutig und bildet die Grundlage für die Referenzierung eine CamelCase-Zeichenfolge oder ein relativer oder absoluter Pfad zur Datei sein. 🎜
// package.json
{ 
    "name": "esm-project", 
    "version": "1.0.0", 
    "main": "index.js", 
    "type": "module", 
    ... 
}
🎜Modulreferenz🎜🎜Verwenden Sie require(), um auf ein Modul zu verweisen Als Argument wird ein Modulbezeichner übergeben, wodurch die API eines Moduls in den aktuellen Kontext eingeführt wird. 🎜rrreee🎜Moduldefinition🎜🎜Es gibt Importe und Exporte im aktuellen Kontext als Module Sie müssen das integrierte module.exports-Objekt verwenden, das der einzige Export ist, der vom Modul exportiert wird. 🎜🎜CommonJS-Spezifikation legt fest, dass innerhalb jedes Moduls die Variable module das aktuelle Modul darstellt. Diese Variable ist ein Objekt und ihr exports-Attribut (d. h. module.exports) ist die externe Schnittstelle. Beim Laden eines Moduls wird tatsächlich das Attribut module.exports des Moduls geladen. 🎜rrreee🎜CommonJS Die Eigenschaften des Moduls sind wie folgt: 🎜
  • Jedes Modul hat einen unabhängigen Kontext und der Code innerhalb des Moduls wird unabhängig ausgeführt, ohne den globalen Bereich zu verschmutzen.
  • Module können mehrmals geladen werden, sie werden jedoch nur beim ersten Laden ausgeführt. Beim nachfolgenden Laden desselben Moduls werden die zwischengespeicherten Ergebnisse direkt gelesen wird in module .cache
  • Module werden in der Codereihenfolge geladen.

Modulimplementierung von Node

🎜Node Das Importieren von Modulen erfordert 3 Schritte: Pfadanalyse -> Dateispeicherort -> Kompilieren und ausführen:🎜
  • 🎜Pfadanalyse: Modultyp anhand der Modulidentifikation analysieren. 🎜
  • 🎜Dateispeicherort: Finden Sie den Speicherort des Moduls anhand des Modultyps und der Modulkennung. 🎜
  • 🎜Kompilierung und Ausführung: Kompilieren Sie die Datei zur Ausführung in Maschinencode, was eine Reihe von Transformationen erfordert. 🎜
🎜【Empfohlenes Lernen: „ nodejs-Tutorial》】🎜🎜Modultypen sind in integrierte Module und Benutzermodule unterteilt:🎜
  • 🎜Eingebaute Module: Integrierte In-Module bestehen aus Node und wurden in eine binäre Ausführungsdatei kompiliert. Wenn node ausgeführt wird, wird das integrierte Modul direkt in den Speicher geladen Daher können wir es direkt einführen. Die Ladegeschwindigkeit ist sehr hoch, da für die Ausführung dieser 2-Schritte keine Dateispeicherung und -kompilierung erforderlich ist. 🎜
  • 🎜Dateimodul: Ein mit js oder C++ geschriebenes Erweiterungsmodul muss bei der Ausführung zunächst in binären Maschinencode kompiliert werden. Sie müssen die oben genannten drei Schritte durchlaufen. 🎜
🎜Modulcache🎜🎜Ob es sich um ein integriertes Modul oder ein Dateimodul handelt , node speichert die Ergebnisse nach dem ersten Laden. Wenn das gleiche Modul das nächste Mal geladen wird, wird es zuerst aus dem Cache durchsucht. Wenn es gefunden werden kann, wird es direkt aus dem Cache gelesen Das zwischengespeicherte Ergebnis ist die Modulkompilierung und das ausgeführte Objekt lässt sich von allen Modulen am schnellsten laden. 🎜

路径分析

路径分析依据的是模块标识符,模块标识符有以下几种类型:

  • 内建模块标识,例如fspath等,不需要编译,node运行时被直接载入内存等待导入。
  • 相对路径模块标识:使用相对路径描述的文件模块
  • 绝对路径模块标识:使用绝对路径描述的文件模块
  • 自定义模块标识:通常是node_modules中的包,引入时也不需要写路径描述,node有一套算法来寻找,是所有模块标识中分析速度最慢的。

文件定位

文件定位主要包括文件扩展名分析、目录和包的处理。如果文件定位结束时都没找到任何文件,则会抛出文件查找失败的异常。

文件扩展名分析

由于模块标识可以不添加文件扩展名,因此Node会按.js.json.node的次序依次补足扩展名来尝试加载,尝试加载的过程需要调用fs模块同步阻塞式地判断文件是否存在,因此为了提高性能,可以在使用require()导入模块时,参数带上文件扩展名,这样会加快文件定位速度。

目录、包的处理

在分析文件扩展名时,可能得到的是一个目录,此时Node会将其作为一个包处理,用查找包的规则来查找:在当前目录下查找package.json,获得其中定义的main属性指定的文件名,以它来作为查找的入口,如果没有package.json,则默认将目录下的index当前默认文件名,然后依次查找index.jsindex.jsonindex.node

编译执行

编译和执行是模块导入的最后一个步骤,node会先创建一个Module实例,代表当前模块。它有以下属性:

  • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。
  • module.children 返回一个数组,表示该模块要用到的其他模块。
  • module.exports 表示模块对外输出的值。

通过文件定位得到的信息,Node再载入文件并编译。对于不同的文件扩展名,其载入方法也有所不同:

  • .js文件:通过fs模块同步读取文件后编译执行。
  • .node文件:这是C/C++编写的扩展文件,通过dlopen()方法加载。
  • .json文件:通过fs模块读取后,用JSON.parse()解析返回结果。
  • 其余扩展名一律当.js文件载入

每一个载入的模块都会被缓存,可以通过require.cache来查看。

使用ES-Module

目前,在node中使用ES-Module属于实验性功能,从8.5开始支持,执行时需要加上--experimental-modules参数。从12.17.0 LTS开始,去掉了--experimental-modules ,现在可以通过使用.mjs文件代替.js文件或在package.json中指定 typemodule 两种方式使用。

// package.json
{ 
    "name": "esm-project", 
    "version": "1.0.0", 
    "main": "index.js", 
    "type": "module", 
    ... 
}

ES-Module相比于CommonJSModule机制,最大不同是ES-Module对导出模块的变量、对象是动态引用,而且是在编译阶段暴露模块的导入接口,因此可以进行静态分析;而CommonJS-Module是运行时同步加载,且输出的是导出模块的浅拷贝。除此之外,ES-Module支持加载CommonJS-Module,而反过来则不行。

其次,Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量,这是因为ES-Module顶层this指向undefinedCommonJS模块的顶层this指向当前模块,而这些内部变量作为顶层变量能被直接使用。

CommonJS的内部变量有:

  • argumentsarguments
  • require
  • module
  • exportsm
  • __filename
  • __dirname

总结

  • Node模块的加载是同步的,只有加载完成,才能执行后面的操作。

  • 每一个文件就是一个模块,有自己的作用域。每个模块内部,module对象代表了当前模块,它的exports

    require
  • module

    exportsm
__filename

__dirname

Zusammenfassung

    🎜🎜Node Modul wird synchron geladen. Erst wenn der Ladevorgang abgeschlossen ist, können nachfolgende Vorgänge ausgeführt werden. 🎜🎜🎜🎜Jede Datei ist ein Modul und hat ihren eigenen Umfang. Innerhalb jedes Moduls stellt das module-Objekt das aktuelle Modul dar und sein exports-Attribut dient als Exportschnittstelle des aktuellen Moduls. 🎜🎜🎜🎜Das importierte Modul ist eine flache Kopie des exportierten Moduls. 🎜🎜🎜🎜Weitere Programmierkenntnisse finden Sie unter: 🎜Programmiervideo🎜! ! 🎜

Das obige ist der detaillierte Inhalt vonWas ist die CommonJS-Modulspezifikation? Eine kurze Analyse des Modulmechanismus von Nodejs. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen