Sistema de módulos de Node.js
El sistema de módulos de Node.js es una de sus funcionalidades principales; permite a los desarrolladores organizar el código en unidades pequeñas y reutilizables, denominadas módulos.
Un módulo es un archivo independiente que encapsula una funcionalidad específica y puede ser importado y utilizado en otros archivos.
El sistema de módulos de Node.js es una base importante para construir aplicaciones Node.js, ya que permite a los desarrolladores dividir el código en pequeñas partes reutilizables y mantenibles.
Cada módulo es un archivo JavaScript individual que contiene un conjunto de funciones, objetos o variables relacionados. De este modo, Node.js ofrece un método estructurado para gestionar aplicaciones grandes, haciendo que el código sea más claro y fácil de mantener.
Cada archivo en Node.js puede actuar como un módulo y, por defecto, se encuentra en un ámbito independiente.
El sistema de módulos sigue la especificación CommonJS, pero también admite módulos ES.
Tipos de módulos
Node.js admite los siguientes tipos de módulos:
- Módulos integrados: módulos que vienen con Node.js, como
fs、http、pathetc. - Módulos definidos por el usuario: módulos creados por el desarrollador.
- Módulos de terceros: módulos instalados mediante npm, como
express、lodashetc.
Importar módulos integrados
Los módulos integrados son módulos que vienen con Node.js; ya están incluidos en el entorno al instalar Node.js, por lo que no requieren instalación adicional. Algunos módulos integrados comunes incluyen fs, http, path, os, crypto, etc.
Para importar módulos integrados, solo hay que usarrequire()la función y pasarle el nombre del módulo. Por ejemplo, para importar el módulo de sistema de archivosfs、path、httpse puede hacer lo siguiente:
// 导入文件系统模块
const fs = require('fs');
// 导入路径模块
const path = require('path');
// 导入 HTTP 模块
const http = require('http');Aquí, fs es una cadena que representa el nombre del módulo a cargar. Node.js reconoce automáticamente que es un módulo integrado y carga la funcionalidad correspondiente.
Ejemplo
console.log('Operating System:', os.platform());
console.log('Total Memory:', os.totalmem());
Importar módulos de terceros
Los módulos de terceros son módulos publicados por desarrolladores o por la comunidad de código abierto. Se pueden instalar en el proyecto mediante npm (Node Package Manager). Algunos módulos de terceros comunes son express, lodash, axios, etc.
Después de instalar los módulos de terceros, estos se colocan en el directorio node_modules del proyecto. Para importar un módulo de terceros, se usa igualmente larequire()función, pero se pasa el nombre del módulo. Por ejemplo, para importar el framework express, se puede hacer así:
// 导入第三方模块 express
const express = require('express');
Antes de importar un módulo de terceros, asegúrate de haberlo instalado mediante npm. Por ejemplo, la instalación de express se puede realizar con el siguiente comando:
npm install express
Instalar el módulo de terceros lodash e importarlo para probarlo:
Ejemplo
const array = [1, 2, 3, 4];
const reversedArray = lodash.reverse(array.slice()); // Usar lodash para invertir el array
console.log('Reversed Array:', reversedArray);
Exportación e importación de módulos personalizados (módulos CommonJS)
1. Exportar el módulo:Usarmodule.exportsoexportspara exportar funciones, objetos o variables.
2. Importar el módulo:Usarrequire()para importar el módulo.
Importar módulos
En Node.js, importar un módulo es muy sencillo. A continuación, creamos unmain.jsarchivo e importamos el módulo hello. El código es el siguiente:
Código del archivo main.js:
hello.world();
En el ejemplo anterior, el códigorequire('./hello')importa el archivohello.jsen el directorio actual (./es el directorio actual; la extensión predeterminada de los archivos node.js es.js)。
Node.js proporciona dos objetos: exports y require. exports es la interfaz pública del módulo, y require se utiliza para obtener desde el exterior la interfaz de un módulo, es decir, el objeto exports del módulo obtenido.
A continuación, crearemos el archivo hello.js, cuyo código es el siguiente:
Código del archivo hello.js:
console.log('Hello World');
}
En el ejemplo anterior, hello.js expone world como interfaz de acceso del módulo mediante el objeto exports; en main.js, a través derequire('./hello')se carga este módulo, y luego se puede acceder directamente aexportslas funciones miembro del objeto.
A veces solo queremos encapsular un objeto en un módulo, el formato es el siguiente:
module.exports = function() {
// ...
}
Por ejemplo:
Código del archivo hello.js:
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
De esta manera, podemos obtener directamente este objeto:
Código del archivo main.js:
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
El único cambio en la interfaz del módulo es el uso demodule.exports = Helloen lugar deexports.world = function(){}. Cuando se referencia este módulo desde el exterior, el objeto de su interfaz es el propio objeto Hello que se exporta, no el exports original.
Diferencia entre module.exports y exports:
module.exportsEs la referencia real al objeto exportado.exportsSímodule.exportsEs un atajo de module.exports. No se puede asignar directamenteexports = ..., de lo contrario se romperá la referencia.
Exportación e importación de módulos personalizados (módulos ES)
Los módulos ES utilizanimportyexport, que es el estándar de módulos del JavaScript moderno.
- Los módulos ES usan
importyexportcomo palabra clave; la extensión del archivo debe configurarse como.mjs, o enpackage.jsondeclararlo en"type": "module"。 - Los módulos ES admiten importación estática (
import ... from ...) e importación dinámica (import())。
Ejemplo
export function greet(name) {
return `Hello, ${name}!`;
}
// main.mjs
import { greet } from './myModule.mjs';
console.log(greet('Bob')); // Salida: Hello, Bob!
Resolución de rutas al cargar módulos
1. Módulos del núcleo:Como http y fs, se incluyen durante la instalación de Node.js y se pueden cargar directamente.
const http = require('http');2. Módulos de archivos locales:Se cargan mediante rutas relativas o absolutas; se debe especificar./o/。
const myModule = require('./myModule');3. Módulos de terceros:Se encuentran ennode_modulesel directorio; basta con escribir el nombre del módulo para cargarlo.
const express = require('express');
Caché de módulos
Mecanismo de caché de módulos:Node.js almacena en caché los módulos ya cargados para mejorar el rendimiento. Cuando se vuelve a llamar a require() para el mismo módulo, devuelve directamente el módulo de la caché en lugar de recargarlo.
Actualizar la caché:Para recargar un módulo, se puede eliminar la caché:
delete require.cache[require.resolve('./myModule')];
Dependencias circulares
Cuando dos o más módulos se importan entre sí, se denomina dependencia circular. Node.js puede manejar dependencias circulares simples, pero puede provocar que algunos objetos exportados por los módulos no estén completamente inicializados.
Ejemplo
const b = require('./b');
console.log('a.js:', b.message);
module.exports = { message: 'Hello from a' };
// b.js
const a = require('./a');
console.log('b.js:', a.message);
module.exports = { message: 'Hello from b' };
// main.js
require('./a');
La salida muestra que cuando b.js importa a.js, como a.js aún no se ha ejecutado por completo, a.message es undefined.
Envoltura de módulos y ámbito
Node.js envuelve cada módulo en una función, de modo que cada módulo tiene un ámbito independiente. Ejemplo:
// Node.js 内部将模块包装为类似这样的函数:
(function(exports, require, module, __filename, __dirname) {
// 模块代码在这里
});Esto significa que las variables definidas en un módulo no contaminan el ámbito global.
¿Dónde se colocan los módulos del lado del servidor?
Quizás ya hayas notado que hemos estado usando módulos en el código. Por ejemplo:
var http = require("http");
...
http.createServer(...);
Node.js incluye un módulo llamadohttplo solicitamos en nuestro código y asignamos su valor de retorno a una variable local.
Esto convierte nuestra variable local en un objeto con todos los métodos públicos proporcionados por el módulo http.
La estrategia de búsqueda de archivos del método require en Node.js es la siguiente:
Dado que en Node.js existen 4 tipos de módulos (módulos nativos y 3 tipos de módulos de archivos), aunque el método require es extremadamente simple, la carga interna es bastante compleja y sus prioridades de carga también son diferentes. Como se muestra en la siguiente figura:

Cargar desde la caché de módulos de archivos
Aunque los módulos nativos y los módulos de archivos tienen prioridades diferentes, todos cargan primero, desde la caché de módulos de archivos, los módulos que ya existen.
Cargar desde módulos nativos
La prioridad de los módulos nativos es solo superada por la prioridad de la caché de módulos de archivo. Después de resolver el nombre del archivo, el método require primero verifica si el módulo está en la lista de módulos nativos. Tomando el módulo http como ejemplo, aunque exista un archivo http/http.js/http.node/http.json en el directorio, require("http") no se cargará desde estos archivos, sino desde el módulo nativo.
Los módulos nativos también tienen un área de caché, y también se cargan preferentemente desde la caché. Si el área de caché no se ha cargado, se invoca el método de carga de módulos nativos para cargarlo y ejecutarlo.
Cargar desde archivo
Cuando el módulo de archivo no está en la caché y no es un módulo nativo, Node.js analiza los parámetros pasados al método require y carga el archivo real desde el sistema de archivos. Los detalles de empaquetado y compilación durante el proceso de carga ya se presentaron en la sección anterior; aquí describiremos en detalle el proceso de búsqueda de módulos de archivo, entre los cuales hay algunos detalles que vale la pena conocer.
El método require acepta la transmisión de los siguientes tipos de parámetros:
- http, fs, path, etc., módulos nativos.
- ./mod o ../mod, módulos de archivo con ruta relativa.
- /pathtomodule/mod, módulos de archivo con ruta absoluta.
- mod, módulos de archivo que no son módulos nativos.
El orden de ejecución de la declaración require(X) bajo la ruta Y:
1. 如果 X 是内置模块 a. 返回内置模块 b. 停止执行 2. 如果 X 以 '/' 开头 a. 设置 Y 为文件根路径 3. 如果 X 以 './' o '/' or '../' 开头 a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 4. LOAD_NODE_MODULES(X, dirname(Y)) 5. 抛出异常 "not found" LOAD_AS_FILE(X) 1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。 2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。 3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。 4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。 LOAD_INDEX(X) 1. 如果 X/index.js 是一个文件, 将 X/index.js 作为 JavaScript 文本载入并停止执行。 2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。 3. 如果 X/index.node 是一个文件, 将 X/index.node 作为二进制插件载入并停止执行。 LOAD_AS_DIRECTORY(X) 1. 如果 X/package.json 是一个文件, a. 解析 X/package.json, 并查找 "main" 字段。 b. let M = X + (json main 字段) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I - 1 5. return DIRS
Otras extensionesEl uso de exports y module.exports
Si desea exponer propiedades o métodos al exterior, useexportseso es suficiente. Para exponer un objeto (similar a una clase, que contiene muchas propiedades y métodos), usemodule.exports。