- Aprender
- Informática
- Programación
- Programación en JavaScript
- Clases y módulos en JavaScript
Clases y módulos en JavaScript: herramientas de desarrollo
package.json y usa las herramientas habituales de un flujo de trabajo moderno: un analizador y un formateador de código, un servidor de desarrollo y un empaquetador de módulos, y un ejecutor de pruebas. Para terminar, descubre qué lugar ocupan TypeScript y los marcos de trabajo de interfaz en esa cadena de herramientas.PrincipianteUnidad 9 de 9 del curso Curso de Programación en JavaScript
Lo que aprenderás
- Usa clases para crear objetos con comportamiento compartido, protege sus datos, construye jerarquías mediante herencia y explica la cadena de prototipos en la que se basan las clases.
- Define una clase y crea objetos con newEscribe una clase con un constructor, campos, métodos y métodos de lectura y escritura, crea instancias con
newy manténthisapuntando al objeto correcto. - Protege los datos con miembros privados y comparte valores con miembros estáticosOculta el estado interno de un objeto mediante campos y métodos privados, y coloca los valores y funciones auxiliares que pertenecen a la clase en su conjunto en miembros estáticos.
- Construye jerarquías de clases con extends y superCrea subclases que hereden y sobrescriban comportamientos, llama a la clase padre con
super, comprueba tipos coninstanceofy decide cuándo conviene más usar herencia o composición. - Explica los prototipos en los que se basan los objetos y las clasesDescribe cómo encuentra JavaScript las propiedades a lo largo de la cadena de prototipos, cómo las clases y
extendsestablecen esa cadena y por qué se dejan sin modificar los prototipos integrados.
- Define una clase y crea objetos con new
- Divide un programa en módulos que exportan e importan funcionalidades, ejecútalos en navegadores y Node.js, carga código bajo demanda y asigna a cada módulo una tarea clara.
- Exporta e importa código entre archivosDivide un programa en módulos de ECMAScript y conéctalos con exportaciones con nombre y predeterminadas, importaciones con cambio de nombre y de espacios de nombres, y reexportaciones, comprendiendo cómo funcionan el ámbito del módulo y los enlaces dinámicos.
- Ejecuta módulos en el navegador y en Node.jsCarga módulos en una página web con
type="module"desde un servidor local, resuelve rutas de módulos y nombres sin prefijo de ruta, y comprende cómo elige Node.js entre módulos de ECMAScript y CommonJS. - Carga módulos bajo demanda y organiza un proyecto en módulosUsa la importación dinámica con
import()yawaitde nivel superior, divide una aplicación en módulos centrados en una tarea y evita problemas con efectos secundarios e importaciones circulares.
- Exporta e importa código entre archivos
- Describe un proyecto en
package.json, ejecuta sus tareas con scripts de npm e instala, actualiza y elige dependencias para que cada computadora obtenga el mismo conjunto funcional de paquetes.- Configura un proyecto con npm y package.jsonCrea un proyecto con
npm init, lee y edita los campos principales depackage.json, ejecuta tareas con scripts de npm ynpx, e interpreta los números de versión semántica. - Instala, actualiza y elige dependenciasInstala paquetes como dependencias de ejecución o de desarrollo, interpreta rangos de versiones, usa
package-lock.jsonynpm cipara instalaciones reproducibles, mantén las dependencias actualizadas y elige paquetes de forma segura.
- Configura un proyecto con npm y package.json
- Usa un analizador y un formateador de código para detectar problemas y mantener un estilo coherente, un servidor de desarrollo y un empaquetador de módulos para ejecutar y distribuir un proyecto, y un ejecutor de pruebas para comprobar que el código funciona.
- Detecta problemas con un analizador de código y da formato al código automáticamenteUsa ESLint para encontrar posibles errores sin ejecutar el código y Prettier para mantener una presentación coherente, configura ambos y ejecútalos desde scripts y el editor.
- Sirve y empaqueta un proyecto con un servidor de desarrollo y un empaquetador de módulosExplica qué hacen los empaquetadores de módulos y los servidores de desarrollo, ejecuta el ciclo de desarrollo, compilación y vista previa con Vite, y comprende la eliminación de código no utilizado, la minificación, los nombres de archivo con huella del contenido, los mapas de código fuente y las variables de entorno.
- Comprueba el código automáticamente con un ejecutor de pruebasEscribe pruebas unitarias con aserciones para funciones y clases, ejecútalas con el ejecutor de pruebas integrado de Node.js o Vitest, interpreta los fallos y diseña código fácil de probar.
- Detecta problemas con un analizador de código y da formato al código automáticamente
- Explica qué añaden TypeScript y los marcos de trabajo de interfaz a JavaScript, cómo usan módulos, npm y herramientas de compilación, y cómo funcionan juntas todas las herramientas en un proyecto.
- Explica qué aporta TypeScript a JavaScriptDescribe cómo los tipos estáticos de TypeScript detectan errores antes de que se ejecute el código, cómo se convierte el código TypeScript en JavaScript y cuándo merece la pena adoptar ese paso adicional.
- Sitúa los marcos de trabajo en una cadena de herramientas moderna y completaExplica qué hacen los marcos de trabajo de interfaz y cómo se basan en módulos, npm y empaquetadores de módulos, y reúne las herramientas de esta unidad en un flujo de trabajo cotidiano.
- Explica qué aporta TypeScript a JavaScript
Guía de estudio de Clases y módulos en JavaScript
1Construye objetos que comparten comportamiento
Un objeto simple agrupa valores relacionados. Una clase añade una forma repetible de crear objetos con datos propios y comportamiento compartido. Empieza con constructores y métodos, y después controla los cambios mediante campos privados y una interfaz pública. La herencia permite que las clases relacionadas especialicen ese comportamiento. La cadena de prototipos explica cómo encuentra JavaScript los métodos y por qué una instancia puede usar código almacenado en otro lugar.
1.1 Crea y usa instancias de clase
Una clase describe objetos con la misma estructura y comportamiento. Cada objeto creado a partir de ella es una instancia. Usa una clase cuando varios objetos necesiten las mismas operaciones; un objeto simple suele bastar para un único valor de configuración.
Da a cada objeto sus propios datos
Por convención, los nombres de clase usan PascalCase, como Rectangle. El constructor se ejecuta cada vez que new crea una instancia. Dentro de él, this se refiere al objeto nuevo. Asignar un valor a this.width almacena una propiedad de instancia; un parámetro llamado solo width es únicamente una variable local.
class Rectangle {
constructor(width, height) {
this.width = width; // cm
this.height = height;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
const a = new Rectangle(3, 4);
const b = new Rectangle(5, 2);
a.area(); // 12
a.perimeter(); // 14
b.area(); // 10
Para a.area(), JavaScript llama al método con this establecido en a. Multiplicar su ancho, 3 cm, por su alto, 4 cm, da 12 cm². El perímetro suma los dos lados diferentes y duplica esa suma, lo que da 14 cm. Cambiar b.width solo cambia b.
Los métodos no llevan la palabra clave function ni comas entre ellos. Una clase tiene como máximo un constructor. En una clase sin extends, omitirlo produce un constructor predeterminado vacío. new devuelve la instancia creada automáticamente.
Un campo declara una propiedad en el cuerpo de la clase: unit = "cm"; da a cada instancia su propio valor. label; empieza con undefined. En una clase sin extends, los campos de instancia se inicializan antes de que se ejecute el cuerpo del constructor.
Lee y asigna mediante métodos de acceso
Un método de lectura se ejecuta cuando se lee una propiedad. Con get area() { return this.width * this.height; }, lee a.area sin paréntesis. Es una alternativa al método anterior, que usa a.area().
Un método de escritura se ejecuta al asignar un valor y puede rechazar datos de entrada no válidos. Necesita un almacenamiento separado: set width(value) { this.width = value; } se llama a sí mismo repetidamente hasta que la pila se desborda. Almacena el valor con otro nombre, como el campo privado #width.
Conserva el objeto receptor al pasar un método
class Counter {
count = 0;
increment() { this.count++; }
}
const counter = new Counter();
const inc = counter.increment;
// inc(); // TypeError: this is undefined
const safeInc = () => counter.increment();
safeInc(); // counter.count is 1
Los métodos de clase se ejecutan en modo estricto. Una llamada desvinculada no tiene un objeto receptor antes del punto, por lo que this es undefined. Quien llama a una función de devolución de llamada también puede proporcionar un objeto receptor diferente. Una función envolvente de tipo flecha conserva la llamada sobre counter; counter.increment.bind(counter) crea una función vinculada. Un campo con función flecha, increment = () => { this.count++; };, también captura el this de la instancia.
Llamar a Rectangle(3, 4) sin new lanza un TypeError. Usar una clase antes de que se haya inicializado su declaración lanza un ReferenceError. En esto las clases difieren de las declaraciones de función, a las que se puede llamar antes en un archivo.
JSON conserva los datos, no el comportamiento de la clase. JSON.stringify(a) guarda sus propiedades públicas propias de ancho y alto. Analizar ese texto crea un objeto simple; reconstruye una instancia con new Rectangle(data.width, data.height) para usar sus métodos.
Puntos clave
newcrea una instancia y ejecuta su constructor;thisidentifica el objeto receptor de una llamada a un método.- Los campos contienen los datos de cada instancia; los métodos comunes se comparten.
- Los métodos de lectura se leen como propiedades; los métodos de escritura se ejecutan al asignar un valor y necesitan almacenamiento separado.
- Usa una función envolvente de tipo flecha o una función vinculada cuando una función de devolución de llamada deba conservar su objeto receptor.
- JSON no restaura los métodos de clase al analizar los datos guardados.
1.2 Protege el estado con campos privados
Un objeto puede ofrecer una pequeña interfaz pública y mantener privado su almacenamiento. Esto es la encapsulación. La interfaz decide qué cambios son válidos; la privacidad por sí sola no comprueba un valor pasado a un método de escritura público.
Sitúa la validación en el límite
Un campo privado empieza con # y debe declararse en el cuerpo de la clase. El código dentro de esa clase lo lee mediante this.#target. El código externo no puede leer ni escribir t.#target: eso es un error de sintaxis, no una lectura de propiedad que devuelve undefined.
class Thermostat {
static MIN = 10;
static MAX = 30;
#target = 20;
get target() { return this.#target; }
set target(value) {
if (!Number.isFinite(value) ||
value < Thermostat.MIN ||
value > Thermostat.MAX) {
throw new RangeError("Target must be between 10 and 30 °C");
}
this.#target = value;
}
}
const t = new Thermostat();
t.target = 22;
t.target; // 22
// t.target = 35; // throws RangeError
El método de lectura expone una lectura sin exponer el almacenamiento. El método de escritura rechaza primero los datos de entrada no finitos o no numéricos y después los valores fuera del intervalo de 10 a 30 °C, incluidos ambos límites. Solo un valor aceptado llega a la asignación. Después de un intento rechazado de establecer 35 °C, la temperatura objetivo sigue siendo 22 °C. Una cadena numérica como "22" tampoco supera Number.isFinite.
Los métodos de lectura y escritura usan #target, por lo que asignar un valor a t.target no llama al método de escritura de forma recursiva. Los nombres target y #target son distintos. Un guion bajo inicial, como en _target, solo pide a otros programadores que traten una propiedad pública como interna; JavaScript sigue permitiendo acceder a ella.
Los campos privados no aparecen en Object.keys, for...in ni en la serialización a JSON. Este termostato no tiene campos públicos propios, por lo que JSON.stringify(t) produce "{}". Por tanto, guardar un objeto puede requerir un método público explícito que devuelva los datos que se van a guardar.
Mantén privados los métodos auxiliares internos
Un método como #check(value) { ... } puede contener pasos de validación y se llama dentro de la clase como this.#check(value). No forma parte de la interfaz pública. Dentro de la clase, #target in obj comprueba si un objeto tiene ese campo privado.
Una subclase no puede usar directamente el nombre privado de su clase padre. Puede usar el método público de lectura o escritura de la clase padre, que se ejecuta en la clase que declaró el campo. Esto permite que la clase padre conserve el control de su propio estado.
Sitúa el trabajo compartido en la clase
Un miembro estático pertenece a la clase. Thermostat.MIN es 10, mientras que t.MIN es undefined. Estos límites estáticos son campos públicos; static por sí solo no los convierte en valores de solo lectura.
class Tracked {
static count = 0;
constructor() { Tracked.count++; }
}
new Tracked();
new Tracked();
new Tracked();
Tracked.count; // 3
Cada constructor incrementa un contador de nivel de clase. Un campo de instancia, en cambio, daría a cada objeto un contador separado. Un método de fábrica estático, como static square(side) { return new Rectangle(side, side); }, pertenece al interior de Rectangle y se llama como Rectangle.square(5). Una función auxiliar de comparación estática se puede pasar a sort cuando recibe los objetos comparados como argumentos.
Dentro de un método estático, this es la clase sobre la que se llamó; no es una instancia de rectángulo con un ancho. Un bloque de inicialización static { ... } realiza la configuración una vez cuando se define la clase y puede acceder a los miembros privados de esa clase.
Puntos clave
- JavaScript garantiza la privacidad de los campos
#; un guion bajo es solo una convención. - Valida mediante la interfaz pública antes de cambiar el estado privado.
- Los campos privados no aparecen en la enumeración normal de claves ni en la serialización a JSON.
- Las subclases usan la interfaz pública de la clase padre para trabajar con su estado privado.
- Accede a los miembros estáticos desde la clase; no contienen los datos de cada instancia.
- Un bloque de inicialización estática se ejecuta una vez cuando se define su clase.
1.3 Especializa una clase mediante herencia
La herencia modela una relación es un. Un círculo es una figura, por lo que una clase Circle puede heredar la descripción compartida de una figura y aportar su propio cálculo del área. Una subclase hereda comportamiento sin copiar todos los métodos en su cuerpo.
Inicializa primero la clase padre
class Circle extends Shape convierte a Circle en una subclase y a Shape en su superclase. En el constructor de una subclase, super(...) llama al constructor de la clase padre. Debe ejecutarse antes del primer uso de this. Una subclase sin constructor recibe uno que pasa sus argumentos a la clase padre.
class Shape {
constructor(name) { this.name = name; }
area() { return 0; }
describe() {
return this.name + " with area " +
this.area().toFixed(2) + " cm²";
}
}
class Circle extends Shape {
constructor(radius) {
super("Circle");
this.radius = radius;
}
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle extends Shape {
constructor(width, height) {
super("Rectangle");
this.width = width;
this.height = height;
}
area() { return this.width * this.height; }
}
La llamada new Circle(2).describe() produce "Circle with area 12.57 cm²". Primero, super establece el nombre. El círculo almacena su radio. Después, el método heredado describe() llama a this.area(), que encuentra el método del círculo. toFixed(2) da formato al resultado para mostrarlo. new Rectangle(3, 4).describe() produce "Rectangle with area 12.00 cm²".
Sobrescribe o amplía un método
Una subclase sobrescribe un método al declarar el mismo nombre. Puede llamar a la versión de la clase padre mediante super.methodName().
class Square extends Rectangle {
constructor(side) {
super(side, side);
this.name = "Square";
}
describe() {
return super.describe() +
" (all sides " + this.width + " cm)";
}
}
new Square(5).describe();
// "Square with area 25.00 cm² (all sides 5 cm)"
super(side, side) inicializa el ancho y el alto con el mismo valor. El método de área heredado los multiplica, y el método sobrescrito añade información a la descripción heredada. Olvidar los argumentos necesarios de la clase padre puede dejar sus propiedades con undefined; llamar a super() no basta si la clase padre necesita valores.
El polimorfismo significa que la misma llamada puede seleccionar distintos comportamientos. Un bucle puede llamar a s.describe() en círculos y rectángulos; cada descripción llama al método area() adecuado. Añadir una figura con su propio método de área no requiere un caso nuevo en ese bucle.
Tanto new Circle(2) instanceof Circle como new Circle(2) instanceof Shape son verdaderos. Los miembros estáticos también se heredan. La relación de una instancia con una clase sigue su cadena de prototipos, que es lo que instanceof comprueba normalmente.
Elige una relación útil
Una clase solo puede extender una clase padre. Para una relación tiene un, usa composición: un automóvil tiene un motor y pide a this.engine.start() que haga un trabajo. Compartir solo un método auxiliar no es motivo para afirmar que una clase no relacionada es un tipo de otra. Una mezcla de métodos copia un grupo de métodos a un prototipo, por ejemplo con Object.assign(Rectangle.prototype, serializable). Las cadenas profundas de clases padre pueden dificultar el seguimiento de los cambios.
Las clases integradas también se pueden extender. Un constructor de ValidationError extends Error llama a super(message), establece this.name = "ValidationError" y puede almacenar this.field = field. Un bloque catch puede usar err instanceof ValidationError para manejar ese fallo de forma diferente y leer el mensaje heredado.
Puntos clave
- Usa
extendspara una relación «es un» y composición para una relación «tiene un». - Llama a
super(...)con los argumentos necesarios de la clase padre antes de usarthis. - Un método sobrescrito puede reutilizar el comportamiento de la clase padre con
super.methodName(). - El polimorfismo permite que una misma llamada a un método funcione en distintas clases relacionadas.
1.4 Sigue la búsqueda de propiedades por los prototipos
Un objeto tiene un enlace oculto a otro objeto, su prototipo, o a null. Al leer una propiedad que falta, se sigue ese enlace. JavaScript continúa buscando hasta encontrar la propiedad o llegar a null; una búsqueda sin resultado devuelve undefined.
Distingue los valores almacenados de los heredados
const defaults = { theme: "light", fontSize: 16 };
const settings = Object.create(defaults);
settings.fontSize = 18;
settings.theme; // "light"
settings.fontSize; // 18
defaults.fontSize; // 16
Object.hasOwn(settings, "theme"); // false
"theme" in settings; // true
Object.keys(settings); // ["fontSize"]
Object.create(defaults) crea un objeto vacío cuyo prototipo es defaults. Asignar un valor a fontSize crea una propiedad propia que oculta el valor heredado. Deja defaults.fontSize sin cambios. Después de delete settings.fontSize, la búsqueda vuelve a encontrar el 16 heredado.
| Comprobación | Propiedades que considera |
|---|---|
Object.hasOwn(obj, key) | Propiedades propias, enumerables o no |
Object.keys(obj) | Claves de cadena propias y enumerables |
key in obj | Propiedades propias y heredadas, enumerables o no |
for...in | Claves de cadena enumerables, incluidas las heredadas |
Una propiedad propia que falta aún puede existir en la cadena. Los métodos de clase no son enumerables, por lo que for...in no suele enumerarlos. Usa Object.getPrototypeOf(settings) para leer su prototipo. Prefiere estos métodos de Object al antiguo accesor __proto__.
Observa cómo las clases comparten métodos
Para una instancia de rectángulo r, Object.getPrototypeOf(r) === Rectangle.prototype es verdadero. Su ancho es una propiedad propia, mientras que area suele estar almacenado en Rectangle.prototype. Object.hasOwn(r, "area") es falso aunque r.area() funcione. Un campo con función flecha es diferente: crea una propiedad de función propia en cada instancia.
Un método heredado sigue recibiendo como this el objeto situado antes del punto. Por tanto, el método de área compartido puede leer dimensiones diferentes para cada rectángulo sin almacenar un método separado en cada objeto.
En la jerarquía de figuras, la búsqueda sigue esta cadena:
circle -> Circle.prototype -> Shape.prototype
-> Object.prototype -> null
Primero se encuentra el método de área del círculo. Su descripción procede de Shape.prototype y toString puede proceder de Object.prototype. La presencia de Shape.prototype en la cadena también explica por qué circle instanceof Shape es verdadero.
El nombre Rectangle.prototype designa el prototipo que se da a las instancias de rectángulo. No designa el prototipo oculto de la propia función Rectangle; para esta clase es Shape cuando extiende Shape, o Function.prototype si no tiene superclase.
Reconoce el código antiguo con constructores
function Rectangle(width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype.area = function () {
return this.width * this.height;
};
Las funciones constructoras usan la misma estructura básica de instancia y prototipo. La sintaxis de clase añade reglas: se requiere new, el cuerpo se ejecuta en modo estricto, los métodos no son enumerables y hay miembros privados disponibles. typeof Rectangle es "function" en ambas formas.
Los arrays encuentran map en Array.prototype; los métodos de cadenas proceden de String.prototype. Evita añadir métodos personalizados a los prototipos integrados: las bibliotecas y los estándares futuros pueden usar los mismos nombres con un comportamiento diferente. El uso previo de flatten en una biblioteca contribuyó a que el método estándar de array se llamara flat.
La asignación a una propiedad de datos normal crea o cambia una propiedad propia, pero un método de escritura heredado puede interceptarla. Cambiar el prototipo de un objeto existente con Object.setPrototypeOf es válido, pero puede perjudicar el rendimiento; elige el prototipo al crear el objeto.
Puntos clave
- La lectura de propiedades busca en el objeto, sus prototipos y, finalmente,
null. - Una propiedad propia puede ocultar un valor heredado sin cambiar el prototipo.
- Que una propiedad sea propia y que sea enumerable son cuestiones diferentes.
- Los métodos de clase comunes están en el prototipo de la instancia y comparten comportamiento.
- Un método heredado usa la instancia receptora como
this. - Las funciones constructoras y las clases se basan en el mismo mecanismo de prototipos.
- Deja los prototipos integrados sin modificar para evitar conflictos de nombres y comportamiento.
2Conecta archivos con módulos de ECMAScript
Los módulos permiten que un programa crezca más allá de un solo script y que cada archivo siga siendo comprensible. Las exportaciones describen lo que ofrece un archivo y las importaciones muestran lo que necesita. Aprende cómo funciona esa conexión, cómo la cargan los navegadores y Node.js, y cómo cargar una funcionalidad solo cuando se solicita. Los límites claros entre los datos, el almacenamiento y las actualizaciones de la página también facilitan las pruebas y ayudan a evitar dependencias circulares.
2.1 Exporta funcionalidades e importa sus enlaces
Un módulo de ECMAScript tiene su propio ámbito. Sus declaraciones de nivel superior permanecen dentro del archivo a menos que se exporten. Los archivos que importan solicitan funcionalidades concretas, lo que hace visibles las dependencias sin poner todo en variables globales.
Relaciona cada forma de exportación
// geometry.js
export const round2 = (n) => Math.round(n * 100) / 100;
export function circleArea(r) {
return Math.PI * r * r;
}
// shapes.js
import { circleArea, round2 } from "./geometry.js";
export default class Circle {
constructor(r) { this.r = r; }
area() { return round2(circleArea(this.r)); }
}
// main.js
import Circle from "./shapes.js";
console.log(new Circle(3).area()); // 28.27
El módulo de entrada importa una clase. El módulo de la clase importa dos funciones auxiliares. Para un radio de 3 cm, el área es aproximadamente 28,274 cm²; la función auxiliar de redondeo produce 28,27. El módulo de entrada no obtiene automáticamente acceso a las funciones auxiliares usadas dentro de otro módulo.
| Exportación | Importación correspondiente |
|---|---|
export const MAX = 30; | import { MAX } from "./limits.js"; |
export default class Circle {} | import Circle from "./shapes.js"; |
Una exportación predeterminada más round2 con nombre | import Circle, { round2 } from "./shapes.js"; |
Un módulo puede tener muchas exportaciones con nombre, pero como máximo una exportación predeterminada. Las importaciones con nombre usan el nombre exportado entre llaves; quien importa elige el nombre local de una importación predeterminada. Escribir import { Circle } no solicita la exportación predeterminada, aunque la clase exportada se llame Circle.
Cambia nombres, agrupa y reexporta
import { circleArea as areaOfCircle } from "./geometry.js"; da otro nombre local a una funcionalidad importada. export { circleArea as area }; cambia su nombre de exportación. Una lista de exportación también puede exponer declaraciones hechas antes en el archivo.
import * as geometry from "./geometry.js"; crea un objeto de espacio de nombres del módulo. Llama a geometry.circleArea(3). Si el módulo tiene una exportación predeterminada, el espacio de nombres la incluye en la propiedad default.
Un archivo puede reunir las exportaciones de otros archivos: export { circleArea } from "./geometry.js"; transmite una exportación con nombre. export * from "./geometry.js"; transmite las exportaciones con nombre, pero no la exportación predeterminada de ese módulo.
Comprende la evaluación compartida y los enlaces dinámicos
Los módulos se ejecutan en modo estricto y tienen undefined como this de nivel superior. Las dependencias se evalúan antes que el cuerpo del módulo que las importa. Importar el mismo módulo resuelto desde varios archivos reutiliza un único módulo evaluado y su estado.
Supón que un módulo exporta let count = 0 y que increment() lo cambia. Después de que un archivo que lo importa llame a increment(), su count importado se lee como 1. Las importaciones son enlaces dinámicos, no copias instantáneas. El archivo que importa no puede reasignar el propio enlace. Si una exportación es un objeto, sigue siendo posible cambiar una de sus propiedades y los demás archivos que lo importan ven el cambio.
Mantén las declaraciones de importación estática en el nivel superior del módulo. Una importación estática dentro de un if o una función es un error de sintaxis; la importación dinámica import() se encarga de la carga posterior. Incluye .js en las rutas relativas de módulos de ECMAScript para el navegador y Node.js: estos cargadores no añaden la extensión. Un error en la ruta o una confusión entre exportaciones con nombre y predeterminadas impide cargar el módulo, así que corrige la importación antes de buscar un error en el código que debía ejecutarse después.
Puntos clave
- Los módulos mantienen locales las declaraciones de nivel superior a menos que se exporten.
- Las importaciones con nombre usan llaves; una importación predeterminada no las usa.
- Un espacio de nombres agrupa exportaciones, mientras que las reexportaciones transmiten la interfaz de un módulo.
- Las importaciones son enlaces dinámicos de solo lectura al estado compartido de un módulo.
- Las importaciones estáticas van en el nivel superior y las rutas relativas de módulos de ECMAScript necesitan extensiones.
2.2 Carga módulos en navegadores y Node.js
La misma sintaxis de módulos de ECMAScript se ejecuta en un navegador y en Node.js. Cada entorno sigue necesitando saber que un archivo es un módulo y tiene su propia forma de resolver las rutas solicitadas.
Inicia un módulo en el navegador
<script type="module" src="main.js"></script>
Carga solo el módulo de entrada con una etiqueta de script. Sus importaciones obtienen los demás archivos automáticamente. Los scripts de módulo sin async son diferidos: se ejecutan después del análisis del HTML, en el orden del documento. Los elementos de la página a los que acceden están disponibles entonces.
Sirve la carpeta mediante HTTP con un servidor local, como python3 -m http.server o npx serve, y abre su dirección localhost. Abrir el HTML desde el disco suele provocar que las solicitudes de módulos fallen debido a las reglas de seguridad del navegador. El servidor también debe devolver JavaScript con un tipo MIME adecuado, como text/javascript.
Las declaraciones de un módulo no se convierten en globales. Un manejador integrado como onclick="save()" no puede encontrar una función local de un módulo. En su lugar, registra el manejador desde JavaScript:
const button = document.querySelector("#save");
function save() { console.log("Saved"); }
button.addEventListener("click", save);
Resuelve rutas y nombres de paquetes
El especificador de módulo es la cadena que nombra el módulo. ./geometry.js es relativo al archivo que importa; ../geometry.js sube una carpeta. Una ruta que empieza por / parte de la raíz del sitio web, mientras que una URL HTTPS completa indica su ubicación completa.
Un nombre sin prefijo de ruta como "date-fns" no tiene un prefijo de ruta. Un navegador no puede buscarlo en node_modules. Un mapa de importaciones colocado antes de los scripts de módulo asigna un nombre sin prefijo de ruta a una URL. En su lugar, un servidor de desarrollo o un empaquetador de módulos puede resolver los paquetes instalados y prepararlos para el navegador. Instalar un paquete por sí solo no cambia el cargador del navegador.
Indica explícitamente el formato de módulo de Node.js
| Archivo o ajuste | Formato en Node.js |
|---|---|
.mjs | Módulo de ECMAScript |
.cjs | CommonJS |
.js con "type": "module" | Módulo de ECMAScript |
.js con "type": "commonjs" | CommonJS |
Node.js lee type del package.json padre más cercano. Sin un tipo explícito, las versiones actuales pueden detectar la sintaxis de módulos de ECMAScript en un archivo .js que de otro modo sería ambiguo. Establece "type": "module" en un proyecto nuevo de módulos de ECMAScript para que las herramientas coincidan en el formato previsto. Las extensiones .mjs y .cjs prevalecen sobre ese ajuste.
Con los tres archivos del ejemplo del círculo y { "type": "module" }, node main.js muestra 28.27. Los mismos archivos funcionan mediante un script de módulo en el navegador servido por HTTP.
Reconoce CommonJS y los módulos integrados
CommonJS es el sistema de módulos más antiguo de Node.js. Usa require() para cargar de forma síncrona y module.exports para proporcionar valores. Un módulo de ECMAScript puede importar module.exports de un paquete CommonJS como exportación predeterminada. Node.js actual también puede cargar con require grafos síncronos de módulos de ECMAScript; await de nivel superior en ese grafo impide la carga síncrona.
En un módulo de ECMAScript, usa import { readFile } from "node:fs/promises"; para un módulo integrado de Node.js. require, module y __dirname son variables de CommonJS, no variables globales de módulos de ECMAScript. import.meta.url proporciona la URL del módulo; las versiones recientes de Node.js también proporcionan import.meta.dirname para módulos de archivos locales.
Si se rechaza una sentencia de importación por estar fuera de un módulo, revisa la configuración del cargador: type="module" en el HTML del navegador, o el tipo de paquete o la extensión en Node.js.
Puntos clave
- Un módulo de entrada en el navegador usa
type="module"y normalmente es diferido. - Usa un servidor local y registra los manejadores de eventos desde el módulo.
- Los navegadores necesitan un mapa de importaciones o herramientas de compilación para resolver nombres de paquetes sin prefijo de ruta.
- Node.js usa las extensiones de archivo y el campo
typedel paquete más cercano. - CommonJS usa
requireymodule.exports; los módulos de ECMAScript usanimportyexport.
2.3 Carga funcionalidades bajo demanda y separa responsabilidades
Una importación estática declara una dependencia necesaria como parte de la carga de un módulo. La importación dinámica es una expresión: comienza a cargar cuando la ejecución llega a ella y devuelve una promesa. Úsala para una funcionalidad que muchas visitas nunca necesitan.
Espera el objeto de espacio de nombres
const button = document.querySelector("#show-chart");
button.addEventListener("click", async () => {
try {
const { drawChart } = await import("./chart.js");
drawChart(measurements);
} catch (err) {
console.error("Could not load the chart module", err);
}
});
Aquí, measurements son los datos que ha recopilado la página. El primer clic carga el módulo de gráficos y llama a su función exportada. Los clics posteriores reutilizan el módulo evaluado. La promesa puede rechazarse por un error de carga o evaluación, por lo que el manejador lo captura.
import("./chart.js").drawChart intenta leer una propiedad de la promesa, no del módulo cargado. Espera la promesa con await o usa .then. Para una exportación predeterminada, accede a la propiedad default del espacio de nombres: const { default: Chart } = await import("./chart.js");. A diferencia de una declaración estática, esta expresión puede aparecer dentro de una función o condición, y su ruta puede calcularse en tiempo de ejecución.
Espera durante la configuración del módulo
Un módulo de ECMAScript puede usar await de nivel superior fuera de una función:
const response = await fetch("./config.json");
export const config = await response.json();
Los módulos que dependen de este módulo esperan a que termine su evaluación antes de ejecutar sus propios cuerpos. Los módulos independientes pueden continuar. Por tanto, una solicitud de configuración lenta retrasa a sus dependientes, pero no necesariamente a todas las partes del programa. Los scripts clásicos y CommonJS no permiten await de nivel superior; usa esta funcionalidad del módulo para la configuración que sus dependientes realmente necesitan primero.
Da a cada módulo una tarea clara
Una aplicación de tareas pendientes puede usar estos límites:
| Módulo | Responsabilidad |
|---|---|
todo.js | Exportar la clase Todo |
storage.js | Cargar y guardar datos mediante localStorage |
ui.js | Renderizar la lista en la página |
main.js | Conectar módulos y manejadores de eventos |
Los módulos de datos y almacenamiento no importan la interfaz de usuario. Se pueden probar y reutilizar sin crear elementos de página. Mantener el cálculo y el almacenamiento fuera de los manejadores de eventos también facilita localizar los fallos.
El estado privado de nivel superior se comparte entre los archivos que importan el módulo. En let nextId = 1; export function createId() { return nextId++; }, la variable permanece privada mientras que la función expone un acceso controlado. Dos archivos que la importan y la llaman una vez obtienen 1 y 2, no dos contadores independientes que comienzan en 1.
Controla el trabajo al importar
import "./setup.js"; es una importación por sus efectos secundarios: carga y evalúa el módulo sin solicitar nombres locales. Los efectos secundarios pueden ser útiles para la configuración, pero un módulo reutilizable es más fácil de probar cuando su nivel superior define funcionalidades y sus funciones inician el trabajo explícitamente.
Un archivo de índice o de reexportaciones reúne reexportaciones. Puede simplificar una interfaz, pero los archivos de reexportaciones grandes pueden incorporar código adicional y ocultar un ciclo. Una dependencia circular aparece cuando los módulos dependen unos de otros, directamente o a través de otros módulos. Un ciclo puede funcionar, pero leer un enlace antes de que el módulo que lo exporta lo inicialice causa un ReferenceError. Traslada las definiciones compartidas a un tercer módulo que ambos lados importen para eliminar ese ciclo de dependencia.
Puntos clave
import()devuelve una promesa para el espacio de nombres de un módulo y puede ejecutarse dentro de una función.- Espera la carga dinámica y maneja las promesas rechazadas.
- Await de nivel superior retrasa la evaluación de los módulos dependientes.
- Separa los datos, el almacenamiento, el renderizado y la conexión de eventos en módulos centrados en una tarea.
- Un módulo evaluado puede mantener estado privado compartido por todos los archivos que lo importan.
- Traslada las definiciones compartidas a un tercer módulo para romper una dependencia circular.
3Ejecuta un proyecto reproducible con npm
Un proyecto necesita una descripción clara de sus comandos y de los paquetes de los que depende. npm mantiene esa información en archivos de paquetes e instala el código que describen esos archivos. Aprende a leer los scripts antes de ejecutarlos, a distinguir las herramientas de desarrollo de las dependencias del producto y a usar rangos de versiones junto con un archivo de bloqueo de dependencias. Estas decisiones permiten que otra computadora reproduzca el proyecto sin copiar una carpeta de paquetes instalados.
3.1 Describe y ejecuta un proyecto de npm
npm es el gestor de paquetes incluido con Node.js. El nombre se refiere tanto a una herramienta de línea de comandos como a su registro público de paquetes. Un paquete es una carpeta descrita por package.json; la raíz de un proyecto es la carpeta que contiene ese archivo.
Crea la descripción del proyecto
Ejecuta node -v y npm -v para comprobar las herramientas instaladas. Cambia a la carpeta prevista para el proyecto antes de ejecutar npm init. El comando interactivo hace preguntas; npm init -y acepta los valores predeterminados. Después, edita el archivo para describir el proyecto.
{
"name": "shape-lab",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node src/main.js",
"test": "node --test",
"lint": "eslint ."
},
"dependencies": {},
"devDependencies": {}
}
Esto es JSON, no un literal de objeto de JavaScript. Las claves y las cadenas usan comillas dobles. Los comentarios y las comas finales no son válidos. Un error de análisis sintáctico puede detener npm antes de que ejecute cualquier comando.
El name está en minúsculas y es válido para una URL; los nombres de los paquetes publicados tienen un máximo de 214 caracteres. version describe este proyecto, no su versión de Node.js. private: true evita la publicación accidental en npm. type: "module" hace que Node.js cargue los archivos .js del paquete como módulos de ECMAScript.
Los paquetes publicados pueden usar main y exports para definir puntos de entrada para sus usuarios. Una entrada opcional engines, como { "node": ">=22" }, indica las versiones de Node.js admitidas. Lee esos requisitos cuando un proyecto falle en una instalación más antigua.
Usa los scripts como menú de tareas
npm run enumera los scripts disponibles. En este ejemplo, npm run lint ejecuta eslint ., npm start ejecuta la aplicación y npm test ejecuta el ejecutor de pruebas. start y test tienen estas formas abreviadas; otros nombres de tarea suelen usar npm run.
npm añade temporalmente el directorio node_modules/.bin del proyecto a la ruta de búsqueda de comandos. Por tanto, una herramienta instalada localmente puede nombrarse directamente en un script. Todas las personas que usan el proyecto ejecutan la misma herramienta registrada en lugar de depender de una instalación global.
Los argumentos adicionales van después de --: npm run lint -- --fix pasa --fix a ESLint. El script da a la tarea un nombre estable aunque el equipo cambie la herramienta que hay detrás. Empieza por el campo scripts cuando aprendas a trabajar en un proyecto desconocido.
Interpreta los números de versión
El versionado semántico usa MAYOR.MENOR.PARCHE. A partir de 2.4.1, una corrección de errores compatible pasa a ser 2.4.2; una funcionalidad compatible pasa a ser 2.5.0; un cambio incompatible pasa a ser 3.0.0. Una actualización mayor puede requerir cambios en el código que usa el paquete. Antes de 1.0.0, un proyecto está en una fase temprana de desarrollo y las versiones publicadas pueden cambiar con más libertad.
Ejecuta un comando de paquete ocasional
npx serve ejecuta el comando instalado si está disponible localmente. De lo contrario, npx puede pedir descargar el paquete temporalmente y ejecutarlo. Esto es útil para una herramienta que no necesita convertirse en una dependencia permanente del proyecto. Comprueba el nombre exacto del paquete y la confiabilidad del código antes de instalarlo o ejecutarlo: los comandos de paquetes y los scripts de instalación pueden ejecutarse con tus permisos.
Un comando ejecutado en la carpeta equivocada puede fallar porque falta package.json, o una instalación puede crear archivos de paquetes en un lugar no deseado. Comprueba primero la carpeta actual. Mantén los scripts comprensibles y evita trucos de comandos específicos de un sistema operativo cuando el proyecto deba ejecutarse en varios sistemas.
Puntos clave
package.jsondescribe el proyecto, los scripts y las dependencias usando JSON válido.private: trueimpide la publicación en npm;typeestablece el formato de módulo de Node.js.- Usa scripts de npm para ejecutar de forma coherente las herramientas instaladas localmente.
- Las versiones semánticas distinguen los cambios incompatibles, las funcionalidades compatibles y las correcciones.
npxpuede ejecutar un comando local o descargar uno temporalmente.
3.2 Instala y actualiza las versiones adecuadas de los paquetes
Una dependencia es código del que depende un proyecto. npm registra las dependencias directas en package.json, instala los paquetes en node_modules y registra el árbol exacto de dependencias instalado en package-lock.json.
Separa el código del producto de las herramientas de desarrollo
npm install date-fns añade un paquete de ejecución a dependencies. npm install --save-dev eslint añade una herramienta de desarrollo a devDependencies; -D es la forma abreviada. Los analizadores y formateadores de código, los ejecutores de pruebas, los empaquetadores de módulos y TypeScript suelen pertenecer a la lista de desarrollo.
Cuando otro proyecto instala un paquete publicado, npm instala las dependencias de ese paquete, pero no sus dependencias de desarrollo. Una aplicación de navegador puede seguir empaquetando dependencias de ejecución en sus archivos finales aunque la propia herramienta de compilación sea una devDependency.
npm install sin nombre de paquete instala las dependencias enumeradas para el proyecto. npm uninstall <package> elimina un paquete y su entrada. Después de instalar date-fns, Node.js y las herramientas de compilación pueden resolver import { format } from "date-fns";. Un navegador sin herramientas adicionales sigue necesitando un mecanismo de resolución, como un mapa de importaciones o un servidor de desarrollo.
Interpreta el rango permitido
| Entrada | Versiones estables que permite |
|---|---|
^4.17.21 | Al menos 4.17.21, por debajo de 5.0.0 |
~4.17.21 | Al menos 4.17.21, por debajo de 4.18.0 |
4.17.21 | Exactamente 4.17.21 |
^0.3.1 | Al menos 0.3.1, por debajo de 0.4.0 |
El acento circunflejo es la opción predeterminada habitual de npm. Para las versiones iguales o superiores a 1.0.0, permite actualizaciones menores y de parche compatibles. Por debajo de 1.0.0, es más estricto. Para ^2.3.0, 2.3.5 y 2.9.0 cumplen el rango; 2.2.9 es demasiado antigua y 3.0.0 supera el límite de la versión mayor.
Un rango permitido no identifica una única versión instalada. El archivo de bloqueo sí lo hace, incluidas las dependencias transitivas, que son los paquetes que necesitan los paquetes elegidos.
Reproduce una instalación
Comparte package.json y package-lock.json con el proyecto. Deja que npm mantenga el archivo de bloqueo. La carpeta generada node_modules se puede reconstruir y no es el archivo que se debe compartir.
Usa npm install durante el desarrollo, cuando los registros de dependencias pueden cambiar. Usa npm ci para una instalación limpia y reproducible: requiere el archivo de bloqueo, elimina el node_modules existente, instala el árbol bloqueado y no reescribe ninguno de los dos archivos de paquetes. Falla si el archivo de paquetes y el de bloqueo no coinciden. Corrige esa discrepancia de forma deliberada durante el desarrollo; eliminar el archivo de bloqueo puede cambiar silenciosamente muchas versiones.
Actualiza con un motivo
npm outdated compara las versiones actual, permitida y más reciente. La permitida es la versión más reciente que admite el rango declarado; la más reciente puede ser una versión mayor fuera de él. npm update actualiza dentro de los rangos y registra el árbol resultante. Para pasar deliberadamente a una versión mayor, lee el registro de cambios, ejecuta npm install <package>@latest y comprueba el proyecto con sus pruebas.
Instala las herramientas del proyecto localmente para que se registren sus versiones. Antes de añadir otra dependencia, comprueba su mantenimiento, adopción y licencia, y si una funcionalidad integrada ya resuelve el problema. structuredClone() cubre las necesidades de copia profunda compatibles; Intl.DateTimeFormat da formato a fechas sin un paquete de fechas.
npm audit informa de vulnerabilidades conocidas en las versiones instaladas. Lee el informe para entender el código afectado y su uso. npm audit fix puede aplicar correcciones compatibles, pero no demuestra que se hayan resuelto todas las vulnerabilidades ni que una actualización conserve el comportamiento del proyecto.
Puntos clave
- Usa
dependenciespara el código de ejecución ydevDependenciespara las herramientas de desarrollo y compilación. - Los rangos de versiones permiten determinadas versiones; el archivo de bloqueo registra las versiones exactas instaladas.
- Comparte ambos archivos de paquetes y reconstruye
node_modules. npm ciinstala lo indicado en el archivo de bloqueo sin cambiarlo y rechaza los archivos de paquetes que no coinciden.npm updatese mantiene dentro de los rangos declarados; las actualizaciones mayores requieren una decisión deliberada.- Lee los informes de auditoría y comprueba el comportamiento después de actualizar dependencias.
4Comprueba y compila código con herramientas de desarrollo
Las distintas herramientas responden a distintas preguntas sobre un proyecto. Un analizador de código busca código sospechoso, un formateador lo organiza, las pruebas comprueban su comportamiento y una compilación prepara los archivos del navegador para su publicación. Un servidor de desarrollo agiliza el ciclo de edición. Aprende qué indica cada resultado y qué deja sin comprobar, para no confundir un comando de formato completado correctamente o una página de desarrollo con una prueba de que toda la aplicación funciona.
4.1 Encuentra problemas de código y aplica un formato coherente
Un analizador de código lee el código fuente sin ejecutarlo e informa de posibles errores o patrones arriesgados. Un formateador de código reescribe la presentación de forma coherente. ESLint y Prettier suelen trabajar juntos porque esas tareas resuelven problemas diferentes.
Configura las comprobaciones
npm init @eslint/config@latest pregunta sobre el proyecto, instala paquetes de desarrollo y crea un archivo de configuración. ESLint actual usa configuración plana, normalmente en eslint.config.js.
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
languageOptions: { globals: globals.browser },
rules: {
eqeqeq: "error",
"no-unused-vars": "warn",
},
},
];
La configuración recomendada activa reglas para detectar errores. Las variables globales del navegador indican a ESLint que existen nombres como document. Esto evita tratar una funcionalidad real del entorno como un nombre no definido.
Los niveles de gravedad de las reglas son "off", "warn" y "error", que también se escriben como 0, 1 y 2. Los errores provocan un código de salida de fallo; las advertencias por sí solas no hacen fallar el comando de forma predeterminada. Una advertencia sigue siendo información que hay que leer, aunque el script termine correctamente.
Interpreta un informe de análisis
function total(items) {
let sum = 0;
let unused = 5;
for (const item of items) {
if (item.quantity == "2") sum += item.price;
}
return summ;
}
Con la configuración anterior, se informa de cinco problemas. total nunca se usa. sum se actualiza, pero su valor nunca se usa más allá de esas actualizaciones. unused no se usa. La regla eqeqeq señala == y no-undef detecta summ, escrito incorrectamente. Llamar a esta función fallaría al encontrar ese nombre no definido, pero el analizador puede detectarlo sin llamarla.
Empieza por el nombre y la ubicación de la regla en el informe. Corrige el comportamiento previsto y vuelve a ejecutar el comando. npm run lint -- --fix solicita correcciones automáticas seguras cuando el proyecto tiene un script lint. No puede decidir la lógica de negocio correcta para cada advertencia. Desactiva una regla en una sola línea solo cuando haya un motivo y registra ese motivo junto al comentario.
Deja la presentación a un solo formateador
npm install --save-dev --save-exact prettier
npx prettier . --write
npx prettier . --check
--write cambia los archivos; --check informa de las diferencias de formato sin cambiarlos. Con los valores predeterminados, const point={x:1,y:2} se convierte en const point = { x: 1, y: 2 };. Las opciones como "singleQuote": true van en .prettierrc.
| Herramienta | Pregunta principal |
|---|---|
| ESLint | ¿Contiene este código un patrón señalado por una regla? |
| Prettier | ¿Tiene el código la presentación elegida? |
| Ejecutor de pruebas | ¿Produce el código el comportamiento esperado? |
Las reglas recomendadas de ESLint no imponen la presentación de Prettier, por lo que esa configuración evita conflictos de formato. Si otra configuración de ESLint añade reglas de estilo que entran en conflicto, eslint-config-prettier las desactiva. Ejecutar dos formateadores que compiten puede hacer que los archivos cambien de un formato a otro repetidamente.
Añade scripts lint y format para que sea fácil repetir las comprobaciones. Las extensiones del editor pueden mostrar diagnósticos mientras escribes y dar formato al guardar después de configurarlas. Ambas herramientas omiten node_modules de forma predeterminada; añade los resultados generados, como dist, a los ajustes de exclusión adecuados. Una función sin problemas de análisis aún puede calcular el perímetro cuando debería calcular el área; el comportamiento necesita pruebas.
Puntos clave
- Un analizador de código encuentra los problemas configurados del código fuente sin ejecutar el programa.
- El nivel de gravedad de error de ESLint hace fallar el comando; las advertencias no lo hacen fallar de forma predeterminada.
- Prettier cambia la presentación con
--writey la comprueba con--check. - Usa reglas compatibles de análisis y formato, además de pruebas de comportamiento.
4.2 Desarrolla localmente y compila archivos para el navegador
Un navegador puede cargar directamente unos pocos módulos de ECMAScript, pero los proyectos más grandes también usan paquetes instalados y archivos que necesitan transformación. Un servidor de desarrollo prepara los archivos mientras editas. Un empaquetador de módulos sigue las dependencias para producir archivos adecuados para su publicación.
Recorre el ciclo de Vite
npm create vite@latest shape-lab
cd shape-lab
npm install
npm run dev
npm run build
npm run preview
Elige la plantilla Vanilla JavaScript para este proyecto. El generador proporciona scripts de desarrollo y compilación. npm run dev muestra una dirección local, a menudo http://localhost:5173. Abre esa dirección mientras editas. En Vite, index.html es el punto de entrada y suele cargar /src/main.js con un script de módulo.
El servidor de desarrollo resuelve los nombres de paquetes y transforma los archivos según sea necesario. Guardar un archivo desencadena una actualización. La sustitución de módulos en caliente, o HMR, reemplaza un módulo modificado en la página en ejecución; las actualizaciones compatibles pueden conservar el estado en lugar de forzar una recarga completa.
npm run build escribe el resultado optimizado en dist. npm run preview sirve ese resultado localmente para que puedas comprobar la compilación real para producción. La vista previa necesita una compilación existente y no crea una nueva. Edita los archivos fuente, ya que otra compilación sustituye el resultado generado.
Comprende los cambios de producción
| Función de compilación | Propósito |
|---|---|
| Empaquetado | Combinar módulos en menos archivos para el navegador |
| Eliminación de código no utilizado | Eliminar código que la compilación puede demostrar que no se usa |
| Minificación | Reducir el tamaño eliminando elementos de presentación y acortando nombres |
| Huellas del contenido | Cambiar los nombres de los recursos cuando cambia su contenido, para facilitar el uso de la caché |
| División del código | Mantener parte del código en fragmentos separados que pueden cargarse después |
Por ejemplo, un módulo auxiliar puede exportar tanto formatArea como formatVolume. Si solo se usa formatArea y es seguro eliminar el otro código, la eliminación de código no utilizado puede omitir formatVolume. Las importaciones y exportaciones estáticas hacen visibles las dependencias. Los efectos secundarios de nivel superior de un módulo pueden tener que permanecer incluso cuando no se usa ninguna de sus exportaciones.
Una import() dinámica da a la herramienta de compilación un punto para cargar código más adelante. Las herramientas de compilación pueden crear fragmentos separados para ella; la distribución de los fragmentos depende de la compilación, por lo que un módulo fuente no siempre se convierte exactamente en un archivo de salida.
Un nombre como assets/index-4f2a9c1b.js cambia con su contenido, lo que permite usar cachés duraderas en el navegador sin servir un archivo antiguo después de una actualización. Un mapa de código fuente conecta las ubicaciones generadas con las líneas del código fuente original, lo que facilita las trazas de la pila y la depuración cuando hay mapas disponibles.
Incluye recursos y configuración pública
Vite también procesa recursos: import "./style.css"; incluye estilos e import logoUrl from "./logo.png"; proporciona la URL final de la imagen. La compilación puede actualizar esas referencias para que coincidan con los nombres de archivo generados.
Lee la información del modo mediante import.meta.env.MODE. Sus valores predeterminados son "development" para desarrollo y "production" para una compilación; un modo personalizado puede usar otro nombre. Los valores de entorno personalizados que empiezan por VITE_ se exponen al código del navegador de forma predeterminada. Todo lo que se envía al navegador es público: mantén las claves secretas y las contraseñas en un servidor, fuera del código del navegador y de las variables de entorno expuestas.
No compruebes solo la página de desarrollo. Compila y previsualiza antes de publicar para encontrar problemas en el resultado transformado y las rutas de los recursos. Abrir dist/index.html directamente desde el disco no reproduce la forma de servir el sitio mediante HTTP. Una página pequeña con unos pocos módulos simples puede necesitar solo un servidor estático, sin una herramienta de compilación completa.
Puntos clave
- Un servidor de desarrollo resuelve y transforma archivos durante la edición; una compilación para producción crea el resultado para publicar.
- HMR puede sustituir el código modificado y conservar el estado compatible de la página.
- La eliminación de código no utilizado solo lo elimina cuando la compilación puede omitirlo de forma segura.
- Las huellas del contenido facilitan el uso de la caché y los mapas de código fuente conectan el resultado con el código fuente.
- Compila primero y después previsualiza localmente el resultado generado.
- Los paquetes para el navegador y los valores de entorno expuestos son públicos.
4.3 Prueba el comportamiento con aserciones
Una prueba automatizada ejecuta código con datos de entrada conocidos y comprueba el resultado mediante aserciones. Una prueba unitaria se centra en una parte pequeña, como una función o un método. El ejecutor encuentra las pruebas, las ejecuta, informa de los fallos y termina con un código de fallo cuando falla una prueba.
Prepara, actúa y verifica
Prepara los datos de entrada, actúa llamando al código y verifica el resultado esperado. Un nombre de prueba útil describe el comportamiento. Para una clase de rectángulo cuyo constructor rechaza un lado negativo:
import { test } from "node:test";
import assert from "node:assert/strict";
import { Rectangle } from "./rectangle.js";
test("area multiplies width by height", () => {
assert.equal(new Rectangle(3, 4).area(), 12);
});
test("rejects a negative width", () => {
assert.throws(() => new Rectangle(-1, 4), RangeError);
});
La primera aserción comprueba 12 cm², un resultado conocido de forma independiente. La segunda pasa una función a assert.throws para que la aserción pueda observar el error. Al ejecutar node --test, se encuentran nombres como rectangle.test.js; un script de paquete "test": "node --test" permite usar npm test.
El modo de aserciones estrictas usa Object.is para assert.equal sin conversión implícita. assert.deepEqual compara el contenido de objetos y arrays. Dos arrays distintos que contienen los mismos valores no son el mismo objeto, por lo que una comparación de identidad estricta no es la comprobación adecuada para contenidos iguales.
Usa ideas equivalentes en Vitest
import { describe, it, expect } from "vitest";
import { Rectangle } from "./rectangle.js";
describe("Rectangle", () => {
it("computes the area", () => {
expect(new Rectangle(3, 4).area()).toBe(12);
});
it("rejects a negative width", () => {
expect(() => new Rectangle(-1, 4)).toThrow(RangeError);
});
});
Vitest usa describe para agrupar e it para pruebas individuales. Ejecuta npx vitest para vigilar los cambios, o npx vitest run para una sola ejecución. Jest ofrece un estilo similar. Usa toEqual para el contenido y toBe para la identidad o los valores primitivos.
Incluye límites y valores numéricos extremos
Prueba los extremos exactos de un intervalo permitido, así como los datos de entrada que quedan justo fuera. Un termostato que acepta de 10 a 30 °C necesita pruebas que acepten 10 y 30, además de pruebas que rechacen 9,5 y 30,5. Añade comprobaciones separadas para valores no numéricos y no finitos si la interfaz los rechaza. Las pruebas no deben depender de un objeto modificado por una prueba anterior ni del orden de ejecución.
La aritmética en coma flotante puede no coincidir exactamente con un decimal escrito: 0.1 + 0.2 es 0.30000000000000004. Para una tolerancia adecuada, usa assert.ok(Math.abs(actual - expected) < 1e-9). Un círculo de radio 3 tiene un área de aproximadamente 28,274333882 cm²; toBeCloseTo(28.27, 2) de Vitest comprueba la proximidad con esa precisión decimal. Elige la tolerancia según lo que la prueba necesite establecer.
Aprende de un fallo
Lee el comportamiento indicado, los valores esperado y real, y la ubicación en el código fuente. Decide si el error está en la implementación o en la expectativa de la prueba. En el desarrollo guiado por pruebas, escribe una prueba que falle, implementa lo suficiente para superarla y después mejora el código manteniendo las pruebas aprobadas.
Exporta la lógica desde los módulos y pasa los datos de entrada como argumentos. Así, un manejador de eventos puede recopilar datos de la página y llamar a esa lógica, en lugar de mezclar cálculos con el trabajo sobre el DOM. El código que depende del DOM puede usar un navegador simulado como jsdom. Espera el trabajo asíncrono en una prueba async para que no pueda terminar antes de sus comprobaciones. Una prueba que simplemente repite la fórmula de la implementación puede repetir su error; usa el comportamiento esperado y ejemplos independientes.
Puntos clave
- Las pruebas ejecutan código y usan aserciones para comprobar el comportamiento esperado de forma independiente.
- Prepara los datos de entrada, actúa y verifica un comportamiento con un nombre de prueba útil.
- Usa comparaciones de contenido para arrays u objetos distintos con el mismo contenido.
- Prueba los límites y los datos de entrada no válidos, y usa tolerancias adecuadas para la coma flotante.
- Espera el trabajo asíncrono y mantén las pruebas independientes del orden de ejecución.
5Sitúa los tipos y los marcos de trabajo en el flujo de trabajo
TypeScript y los marcos de trabajo de interfaz se basan en las herramientas de JavaScript ya presentadas. Los tipos ayudan al editor y al comprobador a encontrar valores incompatibles antes de la ejecución. Los componentes ayudan a organizar interfaces que comparten y modifican datos. Ninguno elimina la necesidad de comprender los módulos, las dependencias o las pruebas. Usa los scripts del proyecto como mapa de cómo colaboran estas piezas y añade herramientas cuando el trabajo les dé un propósito claro.
5.1 Comprende qué comprueba TypeScript
TypeScript añade sintaxis de tipos estáticos a JavaScript. Un tipo describe qué clase de valor espera el código. El comprobador usa esas descripciones antes de que se ejecute el programa, lo que puede revelar errores antes de que un usuario los encuentre.
Comprueba una llamada antes de la ejecución
function area(width: number, height: number): number {
return width * height;
}
area(3, "4"); // type error: the second argument is a string
Ambos parámetros deben ser números. El valor escrito "4" es una cadena, por lo que el comprobador informa de la incompatibilidad. JavaScript simple convertiría implícitamente esa cadena en una multiplicación: 3 * "4" da 12. Con una suma, 3 + "4" da "34". Un error de tipo identifica una incompatibilidad en los datos de entrada previstos aunque JavaScript pudiera ejecutar la expresión.
Una interfaz describe la estructura de un objeto:
interface Shape {
name: string;
area(): number;
}
Una clase declarada con implements Shape debe proporcionar las propiedades y métodos especificados con tipos compatibles. El comprobador también puede detectar propiedades mal escritas, cantidades incorrectas de argumentos y posibles valores undefined cuando está activada la comprobación estricta.
Separa la comprobación de la eliminación de tipos
| Herramienta o acción | Qué hace |
|---|---|
tsc | Comprueba los tipos y normalmente genera JavaScript |
tsc --noEmit | Comprueba los tipos sin escribir archivos de salida |
| Transformación de TypeScript de Vite | Elimina la sintaxis de tipos para la ejecución |
| Eliminación de anotaciones de tipo de Node.js | Ejecuta TypeScript compatible cuya sintaxis de tipos se puede eliminar, después de eliminarla |
Los ajustes de un proyecto TypeScript están en tsconfig.json. Una compilación de Vite no establece por sí misma que todos los tipos sean correctos. Los proyectos suelen ejecutar tsc --noEmit por separado. Node.js actual puede ejecutar directamente archivos .ts compatibles, pero su eliminación de anotaciones de tipo tampoco comprueba los tipos.
Mantén las comprobaciones en tiempo de ejecución en los límites de los datos
Las anotaciones de tipo se eliminan antes de la ejecución. Declarar el tipo esperado de los datos de fetch() o JSON.parse() no verifica el valor real. Comprueba los datos externos en el código en ejecución antes de confiar en ellos. Un servidor puede enviar una cadena aunque una declaración de tipo indique que un campo es numérico.
private y readonly de TypeScript son restricciones del comprobador; no proporcionan la privacidad de # de JavaScript en tiempo de ejecución. Los campos privados de JavaScript siguen estando protegidos cuando se ejecuta el programa. TypeScript tampoco hace que un programa se ejecute más rápido ni demuestra que sus cálculos sean correctos.
El tipo any desactiva comprobaciones útiles para un valor. Puede silenciar un error y permitir que el fallo original permanezca. Corregir una suposición incorrecta o añadir una comprobación real de los datos proporciona mejor información al comprobador.
Adóptalo en pasos manejables
Un archivo JavaScript puede empezar con // @ts-check y usar JSDoc como /** @param {number} width */ para obtener comprobaciones en editores compatibles sin convertirse en un archivo .ts. Más adelante, un proyecto puede cambiar la extensión de los archivos uno por uno. Las bibliotecas pueden proporcionar definiciones de tipos .d.ts; las definiciones de la comunidad suelen distribuirse en @types/....
Los proyectos más grandes y duraderos obtienen interfaces más claras, refactorizaciones más seguras y un mejor autocompletado en el editor. Puede que un script pequeño todavía no justifique otro paso de compilación y una sintaxis nueva. Las pruebas siguen siendo útiles en ambos casos: una función de área con tipos correctos aún puede devolver un cálculo numérico equivocado.
Puntos clave
- TypeScript comprueba los tipos de valor esperados antes de la ejecución.
- Eliminar la sintaxis de tipos es distinto de comprobar los tipos.
- Las declaraciones de tipos no validan datos externos mientras se ejecuta el programa.
- La privacidad de
#en JavaScript se garantiza en tiempo de ejecución;privatede TypeScript se comprueba estáticamente. - La comprobación con JSDoc permite una adopción gradual antes de convertir archivos a TypeScript.
5.2 Combina herramientas según las necesidades del proyecto
Una biblioteca proporciona código al que llama tu programa. Un marco de trabajo proporciona una estructura de aplicación y llama a tu código en los momentos adecuados. Este cambio de control ayuda a coordinar interfaces más grandes, pero sigue basándose en JavaScript, módulos y una cadena de herramientas de proyecto.
Organiza una interfaz en componentes
React, Vue, Svelte y Angular organizan las interfaces de usuario mediante componentes: partes reutilizables como una barra de navegación o un elemento de una lista de tareas. Un componente combina marcado y comportamiento, a menudo con estilos. Cuando cambia su estado, el sistema de interfaz de usuario coordina las actualizaciones pertinentes de la página.
Los metamarcos de trabajo como Next.js, Nuxt, SvelteKit y Astro añaden una configuración más amplia, que incluye enrutamiento, renderizado en el servidor y soporte para la compilación. Sus funciones exactas difieren. Conocer las funciones, los eventos, el código asíncrono y los límites entre módulos de JavaScript ayuda al aprender cualquiera de ellos.
Una página con unas pocas funcionalidades interactivas independientes puede quedar clara con JavaScript simple. Un marco de trabajo resulta útil cuando muchas vistas deben compartir datos cambiantes. Elígelo por esa necesidad en lugar de suponer que todas las páginas requieren la cadena de herramientas más grande.
Sigue cómo colaboran las herramientas
Los paquetes de los marcos de trabajo se instalan mediante npm y se importan como módulos. Las herramientas de compilación transforman sintaxis como JSX o archivos específicos de cada marco de trabajo. TypeScript puede comprobar las interfaces entre las partes. El análisis de código, el formato y las pruebas mantienen sus funciones distintas independientemente de la interfaz elegida.
Inicia un proyecto sencillo y añade las herramientas que usa el equipo:
npm create vite@latest shape-lab
cd shape-lab
npm install
npm install --save-dev eslint @eslint/js globals prettier vitest
Una plantilla de Vite proporciona herramientas de desarrollo y compilación. Instalar paquetes adicionales no los configura: añade la configuración de ESLint, crea pruebas y configura las extensiones del editor para analizar el código y darle formato al guardar. Un campo de scripts puede proporcionar estas tareas:
{
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"format": "prettier . --write",
"test": "vitest run"
}
Estas entradas van dentro de "scripts" en package.json. Mientras editas, ejecuta npm run dev; el editor puede analizar el código y darle formato cuando cambien los archivos. Antes de compartir el trabajo, ejecuta npm test. Antes de publicar, ejecuta npm run build y npm run preview. Después se puede subir la carpeta compilada dist de un sitio estático a un servicio de alojamiento estático adecuado.
Encuentra el archivo que controla una tarea
| Archivo o carpeta | Función |
|---|---|
package.json | Comandos y rangos de dependencias directas |
package-lock.json | Árbol exacto de dependencias |
node_modules/ | Paquetes instalados generados |
eslint.config.js | Reglas y entornos de análisis de código |
.prettierrc | Opciones de formato |
vite.config.js | Opciones de desarrollo y compilación |
tsconfig.json | Ajustes de comprobación y compilación de TypeScript |
dist/ | Resultado generado de la compilación |
Empieza un proyecto desconocido por package.json: sus scripts revelan cómo se ejecuta el proyecto y sus listas de dependencias revelan qué herramientas necesitan esos scripts. Las carpetas generadas se pueden recrear; los ajustes y el código fuente explican cómo hacerlo.
Aprende las herramientas nuevas de una en una para que un error siga teniendo una causa reconocible. Usa la documentación oficial de la versión instalada cuando difieran los comandos o los valores predeterminados, ya que las versiones de las herramientas y los marcos de trabajo cambian con más frecuencia que la sintaxis básica de JavaScript.
Puntos clave
- Tu código llama a una biblioteca; un marco de trabajo llama a tu código dentro de su estructura.
- Los componentes coordinan partes reutilizables de la interfaz y el estado cambiante.
- Los proyectos con marcos de trabajo siguen dependiendo de módulos, paquetes, compilaciones y comprobaciones.
- Las plantillas y la instalación de paquetes no configuran automáticamente todas las herramientas adicionales.
- Lee primero los scripts de paquetes para conocer el flujo de trabajo de un proyecto.
- Añade herramientas para resolver una necesidad concreta y consulta la documentación de la versión instalada.
Ejemplos de Clases y módulos en JavaScript
1Construye una pequeña familia de objetos
18 pasos
- 1IntroducciónEmpieza con objetos que necesitan comportamiento
Un programa puede almacenar muchos rectángulos, pero cada uno también necesita operaciones como el área y el perímetro. Una clase permite que esos objetos compartan las operaciones mientras mantienen sus dimensiones separadas.
Sigue el objeto desde su creación hasta una llamada a un método. Después añade estado protegido, distingue los valores de nivel de clase y especializa el comportamiento mediante herencia. Por último, inspecciona la cadena de prototipos para ver dónde están los métodos compartidos.
- 2EjemploInicializa una instancia
El constructor almacena valores en el objeto nuevo. Aquí las dimensiones están en centímetros:
class Rectangle { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } const a = new Rectangle(3, 4);newcreaay ejecuta el constructor conthisreferido a ese objeto. Las dos asignaciones dan aasu propio ancho y alto. - 3EjemploLlama a un comportamiento compartido con datos separados
Añade
const b = new Rectangle(5, 2);. Ambos objetos pueden llamar aarea, pero cada llamada lee las dimensiones del objeto receptor.a.area()multiplica 3 por 4 y devuelve 12 cm².b.area()multiplica 5 por 2 y devuelve 10 cm². Asignarb.width = 8cambia solob, por lo que su siguiente área es 16 cm² mientras que la deasigue siendo 12 cm².El método no necesita una copia separada de su código para cada rectángulo. Los datos que lee proceden de
this. - 4ConsejoConserva el objeto en una función de devolución de llamada
Separar un método de un objeto no vincula permanentemente su objeto receptor:
const calculate = a.area; // calculate(); // this is undefined const calculateSafely = () => a.area();Los métodos de clase se ejecutan en modo estricto. La llamada desvinculada no tiene ningún objeto antes del punto, por lo que leer
this.widthfalla. La función envolvente de tipo flecha mantiene intacta la llamada al método original.a.area.bind(a)es otra forma de producir una función de devolución de llamada vinculada aa. - 5DesafíoPredice una segunda llamada
Se asigna un nuevo
Rectangle(6, 2)atile. Se ejecuta la sentenciatile.width = 7. ¿Qué devuelvetile.area()y por qué esto no cambia otro rectángulo creado antes?---reveal---
Devuelve 14 cm²: el método lee el ancho actual del objeto receptor, 7 cm, y su alto, 2 cm. Cada instancia almacena sus propias dimensiones, por lo que cambiar
tile.widthno actualiza otra instancia. Compartir un método no hace que los campos de instancia sean compartidos. - 6ExplicaciónAlmacena el estado detrás de un método de escritura
Un termostato puede exponer
targetmientras almacena#targetde forma privada. Un método de lectura lee el valor privado; un método de escritura comprueba un cambio solicitado.El método de escritura rechaza datos de entrada no finitos o no numéricos y valores fuera de 10 a 30 °C antes de asignar
this.#target = value. Ese orden importa: lanzar un error antes de la asignación conserva el último valor objetivo válido. El nombre de almacenamiento separado también evita que un método de escritura se llame a sí mismo repetidamente. - 7EjemploSigue un cambio aceptado y uno rechazado
Empieza con una temperatura objetivo del termostato de 20 °C. Asignar 22 supera ambas comprobaciones, por lo que el método de escritura almacena 22. Asignar después 35 lanza un
RangeError; el valor almacenado sigue siendo 22.Number.isFinite(value)también rechaza"22",NaNe infinito. Las comparaciones de rango por sí solas no rechazarían todos los tipos o valores no válidos. El almacenamiento privado controla el acceso directo, mientras que la validación controla lo que acepta la interfaz pública. - 8Idea claveElige entre almacenamiento de clase o de instancia
Un campo declarado con
staticpertenece a la clase. Lee el límite inferior del termostato comoThermostat.MIN, mientras quet.targetlee el estado de una instancia.Un contador estático puede llevar la cuenta de cuántos objetos se han construido. Un campo normal, en cambio, se inicia por separado en cada objeto nuevo. Dentro de un método estático,
thises la clase que recibe la llamada, por lo que no proporciona la temperatura objetivo ni las dimensiones de una instancia. - 9DesafíoUsa la vía pública
Una subclase de
Thermostatnecesita elevar su temperatura objetivo de 20 a 24 °C. La clase padre declara#targety un método de escritura públicotargetque valida el valor. ¿Cómo debe solicitar la subclase el cambio y qué ocurriría si accediera directamente athis.#target?---reveal---
Asigna
this.target = 24para que el método de escritura heredado valide y almacene el valor. La subclase no puede nombrar directamente el campo privado de la clase padre; ese uso de#targetes un error de sintaxis. Los métodos de acceso heredados siguen ejecutándose en la clase que declaró el estado privado. - 10EjemploDa una clase padre a las clases relacionadas
Un constructor de
Shapealmacena un nombre y su métododescribe()llama athis.area(). Un círculo especializa el cálculo:class Circle extends Shape { constructor(radius) { super("Circle"); this.radius = radius; } area() { return Math.PI * this.radius ** 2; } }superinicializa la parte de la clase padre antes de usarthis.radius. Un círculo es una figura, por lo que la descripción heredada se ajusta a su función. - 11EjemploDeja que el objeto receptor elija el cálculo
Con un radio de 2 cm,
new Circle(2).describe()devuelve"Circle with area 12.57 cm²". El método de descripción es heredado, pero su llamada athis.area()encuentra el método sobrescrito del círculo.Un rectángulo puede heredar esa misma descripción y aportar la multiplicación como su propio método de área. Un bucle que llama a
describe()en ambos objetos no necesita ramas separadas para el círculo y el rectángulo. Esta interfaz compartida con comportamiento diferente es el polimorfismo. - 12ExplicaciónAmplía un método de la clase padre
Un cuadrado puede llamar a
super(side, side)en su constructor para dar a su clase padre de rectángulo dimensiones iguales. Una descripción sobrescrita puede entonces llamar asuper.describe()y añadir la longitud del lado.super(...)en el constructor ysuper.describe()en un método tienen tareas diferentes: uno inicializa la clase padre y el otro invoca un método de la clase padre. Una subclase sin constructor propio pasa los argumentos automáticamente. - 13DesafíoElige un modelo útil
Un objeto
Reportdebe enviar mensajes mediante unNotifier. Los informes no son notificadores. ¿Cómo deben relacionarse los objetos y cómo solicitaría el informe una notificación?---reveal---
Usa composición: da al informe una propiedad que contenga un notificador y después llama a un método como
this.notifier.send(message). Esta es una relación «tiene un». ExtenderNotifierafirmaría que cada informe es en sí mismo un notificador solo para reutilizar su código, lo que no coincide con el modelo. - 14ExplicaciónEncuentra un método a través de la cadena
Las propiedades propias de un círculo contienen datos como su radio. Cuando no tiene un
describepropio, JavaScript busca enCircle.prototypey después enShape.prototype, donde está almacenado ese método.La cadena normal continúa por
Object.prototypehastanull. Si ningún objeto de la cadena tiene una propiedad solicitada, leerla produceundefined.instanceof Shapenormalmente se cumple porqueShape.prototypeaparece en la cadena de la instancia. - 15EjemploOculta un ajuste heredado
Crea
settingsconObject.create(defaults), dondedefaults.fontSizees 16. Asignasettings.fontSize = 18.La asignación crea una propiedad de datos propia que oculta la heredada. El valor predeterminado sigue siendo 16. Eliminar
settings.fontSizeelimina la propiedad que lo ocultaba, por lo que la siguiente lectura vuelve a encontrar 16 en el prototipo. Este seguimiento supone una propiedad de datos normal; en cambio, un método de escritura heredado puede gestionar la asignación. - 16DesafíoComprueba si una propiedad es propia por separado del acceso
Un objeto
panelheredatheme: "light"de un prototipo simple y no tiene unthemepropio. ¿Qué producenpanel.theme,Object.hasOwn(panel, "theme")y"theme" in panel?---reveal---
Producen
"light",falseytrue. La lectura einpueden encontrar propiedades heredadas, mientras queObject.hasOwnsolo pregunta si la propiedad está almacenada en el propiopanel. Una lectura correcta no demuestra que la propiedad sea propia.Object.keys(panel)también omite esta clave heredada. - 17DesafíoRelaciona herencia, privacidad y búsqueda
Sensordeclara#value, un método de lectura públicovaluey un métododescribe()que leethis.value.IndoorSensor extends Sensorno añade miembros. ¿Por qué puede un sensor de interior llamar adescribe()aunque el cuerpo de su clase no pueda nombrar#value?---reveal---
La búsqueda encuentra el método heredado, que se ejecuta con el sensor de interior como
this. El método de lectura declarado enSensorpuede leer su propio campo privado en esa instancia. La herencia proporciona el comportamiento público sin dar a la subclase acceso directo al nombre privado de la clase padre. - 18RepasoInspecciona un objeto en un orden útil
Al seguir una instancia de clase, pregúntate dónde se almacenan sus datos, qué objeto recibe la llamada al método y dónde encuentra la búsqueda el método. Si un cambio pasa por un método de escritura, sigue la validación antes de la asignación.
Para una jerarquía desconocida, inspecciona
extendsy los argumentos del constructor antes de seguir los métodos sobrescritos. Usa comprobaciones de propiedades propias cuando necesites las claves almacenadas y sigue los prototipos cuando necesites explicar el comportamiento heredado.
2Conecta un programa a través de los límites entre módulos
13 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue un grafo de dependencias
- 2EjemploExporta el cálculo
- 3EjemploExporta y usa la clase
- 4DesafíoLee una exportación modificada
- 5EjemploInicia los archivos en un navegador
- 6ExplicaciónResuelve un nombre de paquete
- 7DesafíoElige el cargador de Node.js
- 8EjemploRetrasa la carga de un gráfico opcional
- 9Idea claveIdentifica qué espera a la configuración
- 10DesafíoRompe un ciclo innecesario
- 11ConsejoEvita iniciar trabajo al importar
- 12DesafíoCombina la carga con una interfaz clara
- 13RepasoSigue un error de módulo por etapas
3Reproduce un proyecto de npm en otra computadora
11 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHaz que el proyecto sea reproducible
- 2EjemploCrea la descripción del paquete
- 3EjemploLee la tarea antes de ejecutarla
- 4DesafíoIdentifica un cambio de versión
- 5ExplicaciónRegistra el código y las herramientas por separado
- 6EjemploInterpreta lo que permite un rango
- 7ExplicaciónInstala el árbol registrado
- 8DesafíoInterpreta una instalación limpia bloqueada
- 9ConsejoPlanifica una actualización deliberada
- 10DesafíoCombina un rango y una compilación limpia
- 11RepasoLee juntos los dos registros de paquetes
4Comprueba un proyecto desde la edición hasta la compilación
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHaz una pregunta diferente a cada herramienta
- 2ExplicaciónConfigura un informe útil de análisis de código
- 3EjemploEncuentra un error de escritura antes de la ejecución
- 4DesafíoElige una comprobación de formato
- 5EjemploEjecuta desarrollo, compilación y vista previa
- 6EjemploSigue una optimización de compilación
- 7ConexiónConecta el resultado con el código fuente
- 8DesafíoElige qué volver a compilar
- 9EjemploEscribe una expectativa independiente
- 10Idea claveCompara correctamente el contenido y la identidad
- 11DesafíoPrueba el límite exacto
- 12ConsejoEspera una prueba asíncrona
- 13EjemploUsa una tolerancia numérica razonable
- 14DesafíoInvestiga una compilación correcta con un resultado equivocado
- 15RepasoInterpreta los fallos como evidencia
5Integra tipos y componentes en un proyecto funcional
10 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónAñade una herramienta para una necesidad específica
- 2EjemploDetecta una llamada incompatible
- 3ExplicaciónSepara la transformación de la comprobación
- 4DesafíoComprueba los datos que llegan en tiempo de ejecución
- 5ExplicaciónSitúa un componente en el flujo de trabajo
- 6EjemploLee el menú de tareas del proyecto
- 7DesafíoElige cuándo ayuda un marco de trabajo
- 8DesafíoEncuentra la comprobación que falta
- 9ConsejoAdopta la comprobación gradualmente
- 10RepasoSigue cada herramienta hasta su evidencia
Ejercicios de Clases y módulos en JavaScript
Set 1: Interpreta clases, importaciones y tareas de proyecto10 Preguntas de Práctica
Pregunta 1 de 10
static created = 8;. Su instancia se asigna a batch y la clase se llama Batch. ¿Cómo se lee el contador almacenado?Mostrar respuestaOcultar respuesta
Batch.created. Al leer batch.created, se busca en la instancia y en su cadena de prototipos de instancia, donde no se colocó este campo estático. Estático no significa que se copie un campo en cada objeto.Pregunta 2 de 10
LIMIT por nombre. ¿Cómo puede una sola declaración importar la función como calculate y la constante con su nombre de exportación?Mostrar respuestaOcultar respuesta
{ calculate, LIMIT } pide dos exportaciones con nombre, pero la función se proporcionó como predeterminada. Quien importa elige el nombre local calculate, que no tiene por qué coincidir con el nombre de declaración de la función.Pregunta 3 de 10
¿Por qué falla este constructor y qué orden lo corrige?
class Item {
constructor(code) { this.code = code; }
}
class Box extends Item {
constructor(code) {
this.open = false;
super(code);
}
}
new Box("K7");
Mostrar respuestaOcultar respuesta
this hasta que el constructor de la clase padre haya inicializado la instancia mediante super(code). Mueve esa llamada antes de la asignación de open. Asignar primero otro campo tendría el mismo problema, porque seguiría accediendo a this demasiado pronto.Pregunta 4 de 10
export default class Badge {}. ¿Qué importación vincula esa clase al nombre local Tag?Mostrar respuestaOcultar respuesta
Tag. Importar { Badge as Tag } solicita una exportación con nombre llamada Badge, que este módulo no proporciona. Una importación de espacio de nombres vincularía un objeto que contiene la exportación predeterminada, no la propia clase.Pregunta 5 de 10
import, pero su etiqueta de script no tiene atributo de tipo. ¿Qué cambio en HTML lo convierte en un módulo de ECMAScript?Mostrar respuestaOcultar respuesta
type="module" indica al navegador que use la sintaxis y las reglas de carga de los módulos de ECMAScript. Añadir solo defer cambia el momento de ejecución de un script clásico, pero no hace válidas las declaraciones de importación en él. El módulo de entrada puede entonces cargar sus dependencias automáticamente cuando los archivos se sirven correctamente.Pregunta 6 de 10
"scripts": { "check": "eslint ." }. ¿Qué comando ejecuta esa tarea con nombre?Mostrar respuestaOcultar respuesta
npm run, por lo que esta tarea es npm run check. npm check no adquiere una forma abreviada solo porque un script tenga ese nombre; las conocidas formas npm test y npm start son casos especiales. El script ejecuta el comando ESLint registrado.Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
devDependencies y normalmente se añade con npm install -D. La sección de scripts puede contener un comando que lo ejecute, pero eso no registra la dependencia del paquete. El código de bibliotecas necesario en ejecución pertenece a dependencies.Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Pregunta 9 de 10
"a" y "b". ¿Cómo debería una prueba de Vitest comprobar ese contenido frente a un literal de array separado?Mostrar respuestaOcultar respuesta
toEqual comprueba el contenido del array, incluido el orden de sus valores. toBe y las expresiones de identidad comparan si ambos operandos son el mismo objeto; un literal de array separado crea un objeto diferente. Por tanto, un contenido igual requiere la comparación profunda mostrada.Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Set 2: Aplica módulos y configuraciones de proyecto reproducibles10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
¿Qué ocurre en la última línea?
class Meter {
value = 2;
read() { return this.value; }
}
const meter = new Meter();
const read = meter.read;
read();
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Cuál es el último resultado?
const base = { unit: "cm" };
const sample = Object.create(base);
Object.hasOwn(sample, "unit");
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Qué devuelve la expresión final?
class Label {
constructor(text) { this.text = text; }
length() { return this.text.length; }
}
new Label("Orbit").length();
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
import("./help.js")?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
~6.4.2. ¿Cuál es la versión estable permitida más reciente entre 6.4.9, 6.5.0, 7.0.0 y 6.4.1?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
loadItems() asíncrona sin esperarla. La función de devolución de llamada de la prueba termina; después la promesa se cumple con un array nuevo. La aserción prevista usa toBe(["x"]). ¿Qué debe cambiar para que la prueba compruebe de forma fiable el contenido prometido?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
.ts se ejecuta en Node.js mediante la eliminación compatible de anotaciones de tipo. ¿Por qué esa ejecución correcta no demuestra que todos sus tipos declarados sean correctos?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 3: Sigue el estado, las dependencias y el comportamiento de compilación10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
"8", ¿qué valor permanece y por qué?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Cuáles son los valores finales?
const base = { value: 4, read() { return this.value; } };
const x = Object.create(base);
const y = Object.create(x);
x.value = 7;
y.value = 10;
delete y.value;
[y.read(), Object.hasOwn(y, "read"), base.value];
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Cuáles son los dos valores finales?
const defaults = { size: 11 };
const item = Object.create(defaults);
item.size = 15;
delete item.size;
[item.size, defaults.size];
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
"tiny-calendar". El paquete está instalado localmente, pero el navegador informa de un especificador que no puede resolver. ¿Qué tipo de configuración ausente explica esto?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
main.js importa estáticamente config.js, que espera una respuesta de red antes de exportar ajustes. Un módulo de entrada independiente, clock.js, no importa ninguno de los dos archivos. Más tarde, el manejador asíncrono de main ejecuta const plugin = await import("./plugin.js"), cuya única exportación es la clase predeterminada Plugin. ¿Qué opción describe correctamente la espera y la construcción?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
^8.1.0 y bloquean una dependencia en 8.1.3. Una compilación limpia ejecuta npm ci después de que se publique 8.7.0. ¿Qué versión se instala?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 4: Diagnostica fallos de clases y proyectos10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
¿Cuáles son los valores finales?
class Counter {
count = 1;
add() { this.count += 2; }
}
const a = new Counter();
const b = new Counter();
const addA = a.add.bind(a);
b.count = 10;
addA();
const callB = () => b.add();
callB();
[a.count, b.count];
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
#level y proporciona un método de lectura público en su prototipo. No define ningún método toJSON. ¿Qué produce JSON.stringify(new Gauge())?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
class Photo extends Asset {} se declara sin constructor. Asset almacena el argumento que se pasa a su constructor. ¿Qué ocurre con un argumento en new Photo("P8")?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
let count = 1, un objeto state = { count: 1 } y advance(), que incrementa ambos contadores. Otros dos módulos importan estas mismas exportaciones. El primero llama a advance() y después asigna state.count = 9. ¿Qué lee el segundo como [count, state.count] y puede reasignar count?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
save() y un botón usa el manejador integrado onclick="save()". El módulo se carga correctamente, pero al hacer clic se informa de que save no está definido. ¿Cómo se debe conectar el evento?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
"inspect": "eslint src". ¿Qué comando ejecuta ese script con el argumento --fix de ESLint?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
no-undef. ¿Qué configuración elimina el conflicto de formato y conserva la comprobación útil de errores?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
--write de Prettier?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Una cláusula de guarda debe aceptar longitudes numéricas finitas de 0 a 12 cm, incluidos ambos límites. ¿Qué tres expectativas de prueba revelan fallos distintos en esta implementación?
function setLength(n) {
if (n <= 0 || n > 12) throw new RangeError();
return n;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
dist en ese flujo de trabajo?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 5: Combina las ideas en proyectos desconocidos10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
¿Qué devuelve new LoudNote("A").describe()?
class Note {
constructor(text) { this.text = text; }
label() { return this.text; }
describe() { return "[" + this.label() + "]"; }
}
class LoudNote extends Note {
label() { return super.label() + "!"; }
describe() { return super.describe() + "?"; }
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Qué devuelve la llamada final?
class Base {
constructor(n) { this.n = n; }
value() { return this.n; }
report() { return this.value() + 1; }
}
class Double extends Base {
value() { return super.value() * 2; }
}
class Extra extends Double {
report() { return super.report() + 3; }
}
new Extra(4).report();
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
feature.js tiene una exportación predeterminada y ready con nombre. Un archivo de reexportaciones solo contiene export * from "./feature.js";. ¿Qué transmite el archivo de reexportaciones?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
document en Node.js. ¿Qué reorganización aborda ambos fallos?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
"private": true en package.json?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
^0.6.2, bloquea 0.6.4 y tiene un archivo de bloqueo válido y coincidente. El registro ofrece 0.6.9 y 0.7.0. ¿Qué secuencia describe la versión después de npm ci y después la versión permitida más reciente tras un npm update deliberado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
VITE_. ¿Por qué esa credencial no es adecuada para este lugar incluso después de la minificación?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
RangeError. ¿Qué aserción de Vitest permite que el método de comprobación observe el lanzamiento?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
private value de TypeScript, le asigna datos de entrada analizados de la red y devuelve un resultado numérico con una fórmula incorrecta. El proyecto supera tsc --noEmit. ¿Qué combinación aborda la privacidad en tiempo de ejecución, los datos externos y el comportamiento?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Análisis estático
Analizador de código
Anotación de tipo
Archivo de bloqueo de dependencias
npm ci para recrear el árbol bloqueado en otra computadora.Archivo de reexportaciones
Aserción
Auditoría de dependencias
await de nivel superior
B
Biblioteca
Bloque de inicialización estática
C
Cadena de prototipos
null cuando no existe otro prototipo.Shape.prototype después de que la búsqueda pase por Circle.prototype.Campo de instancia
Campo privado
#, al que solo se puede acceder directamente dentro de la clase que declara ese nombre.Clase
Rectangle puede crear varias figuras cuyos métodos de área usan sus propios anchos y altos.CommonJS
require y module.exports para proporcionar valores..cjs sigue siendo CommonJS aunque el paquete que lo contiene seleccione explícitamente módulos de ECMAScript.Componente
Composición
Configuración plana
Constructor
D
Definición de tipos
Dependencia
Dependencia circular
Dependencia de desarrollo
-D para que su versión quede registrada en el proyecto.Dependencia transitiva
Desarrollo guiado por pruebas
División del código
E
Efecto secundario
Ejecutor de pruebas
Eliminación de anotaciones de tipo
Eliminación de código no utilizado
Empaquetador de módulos
Encapsulación
Enlace dinámico
Espacio de nombres del módulo
default cuando existe una exportación predeterminada.Especificador de módulo
"../utils.js" pide al cargador un módulo situado una carpeta por encima del archivo que importa.Especificador sin prefijo de ruta
Estado
Exportación con nombre
MAX, y quien lo importa puede solicitarlo directamente o cambiarle el nombre localmente.Exportación predeterminada
Circle aunque otro archivo le dé un nombre local diferente.F
Formateador de código
Función constructora
new para inicializar objetos, a menudo junto con métodos asignados a su prototipo.area a Rectangle.prototype después de definir una función constructora.H
Herencia
Huella del contenido
I
Importación dinámica
import() que carga un módulo bajo demanda y devuelve una promesa para su espacio de nombres.Instalación limpia
Instancia
Interfaz
Interfaz pública
M
Mapa de código fuente
Mapa de importaciones
Marco de trabajo
Metamarco de trabajo
Método
tile.area() permite que el método lea las dimensiones de tile mediante el objeto receptor this.Método de escritura
Método de fábrica estático
Rectangle.square(5) para crear una instancia con lados de igual longitud.Método de lectura
thermostat.target puede invocar un método de lectura que devuelve la temperatura objetivo privada almacenada.Método privado
# y que mantiene las operaciones auxiliares fuera de la interfaz pública del objeto.this.#check(value) para validar datos de entrada sin exponer esa función auxiliar al código externo.Mezcla de métodos
Miembro estático
Minificación
Módulo de ECMAScript
import y export, con su propio ámbito y reglas de ejecución estrictas.N
Nivel de gravedad de una regla
npm
npx
O
Objeto receptor
this en una llamada normal a un método.box.open(), el objeto receptor es box; desvincular open no vincula permanentemente ese objeto receptor.P
Paquete
Polimorfismo
describe() en círculos y rectángulos, y cada objeto proporciona el área adecuada.Preparar, actuar y verificar
Propiedad enumerable
Object.keys enumera las claves de cadena propias y enumerables, mientras que for...in también puede incluir las heredadas.Propiedad heredada
Object.hasOwn indica que no tiene una clave de tema propia.Propiedad propia
Prototipo
null, enlazado a otro objeto y en el que se busca cuando ese objeto no tiene una propiedad propia solicitada.Prueba de valores límite
Prueba unitaria
R
Rango de versiones
Reexportación
S
Script de npm
npm run lint.Script diferido
Servidor de desarrollo
Sobrescritura de métodos
area() mientras sigue heredando el método de descripción compartido de la clase padre.Sombreado de variables
Subclase
extends que hereda de una superclase y puede añadir miembros o especializar el comportamiento heredado.Superclase
Sustitución de módulos en caliente
T
Tolerancia
TypeScript
V
Validación en tiempo de ejecución
Versionado semántico
Sobre esta unidad
Crea tus propios Materiales de Estudio
Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.