JS Flashcards
¿Cuál es la diferencia entre let, const y var en JavaScript?
-
var
:- Declaración antigua (antes de ES6).
- Tiene scope de función (no respeta el bloque).
- Se puede redeclarar y reasignar.
- Sus variables son hoisted, pero inicializadas con
undefined
.
-
let
:- Introducido en ES6.
- Tiene scope de bloque (respeta
{}
). - No se puede redeclarar, pero sí reasignar.
- También es hoisted, pero no accesible antes de la declaración (temporal dead zone).
console.log(miVariable); // ReferenceError: Cannot access ‘miVariable’ before initialization
let miVariable = 10;
-
const
:- Introducido en ES6.
- Tiene scope de bloque.
- No se puede redeclarar ni reasignar.
- Las constantes referenciales (objetos/arrays) pueden mutar su contenido, pero no el valor de referencia.
Resumen rápido:
- Usa let
para variables que cambian.
- Usa const
para valores constantes o referencias inmutables.
- Evita var
en proyectos modernos.
¿Cómo se utiliza el método filter() en un array en JavaScript?
-
Descripción:
El métodofilter()
crea un nuevo array con los elementos que cumplen una condición especificada en una función de callback. -
Sintaxis:
javascript array.filter(callback(element, index, array), thisArg);
-
callback: Una función que se ejecuta para cada elemento del array.
- element: El elemento actual.
- index (opcional): El índice del elemento actual.
- array (opcional): El array sobre el que se está iterando.
-
thisArg (opcional): Valor a usar como
this
dentro del callback.
-
callback: Una función que se ejecuta para cada elemento del array.
-
Ejemplo básico:
Filtrar números mayores a 5:javascript const numeros = [1, 4, 6, 8, 3]; const mayoresA5 = numeros.filter(num => num > 5); console.log(mayoresA5); // [6, 8]
-
Ejemplo con objetos:
Filtrar productos con precio mayor a $50:
```javascript
const productos = [
{ nombre: “Laptop”, precio: 1000 },
{ nombre: “Mouse”, precio: 20 },
{ nombre: “Teclado”, precio: 50 },
];const productosCaros = productos.filter(producto => producto.precio > 50);
console.log(productosCaros);
// [{ nombre: “Laptop”, precio: 1000 }]
``` -
Nota importante:
filter()
no modifica el array original. Devuelve un nuevo array.
Explica cómo funciona el mecanismo de hoisting en JavaScript.
-
¿Qué es el hoisting?
Es un comportamiento de JavaScript donde las declaraciones de variables, funciones o clases se “elevan” al principio de su SCOPE ámbito (función o script) durante la fase de compilación. -
Cómo funciona:
-
Variables con
var
:
Solo se eleva la declaración, no la asignación.javascript console.log(miVar); // undefined var miVar = 5;
Internamente, JavaScript lo interpreta así:javascript var miVar; console.log(miVar); // undefined miVar = 5;
-
Variables con
let
yconst
:
También son elevadas, pero permanecen en la Temporal Dead Zone (TDZ) hasta que se ejecuta su declaración.javascript console.log(miLet); // ReferenceError let miLet = 10;
-
Funciones declarativas:
Las declaraciones completas se elevan, por lo que puedes usarlas antes de definirlas.javascript saludar(); // "Hola" function saludar() { console.log("Hola"); }
-
Funciones como expresiones (
var
):
Solo se eleva la declaración, no la asignación.javascript console.log(saludar); // undefined var saludar = function () { console.log("Hola"); };
-
Clases:
Son elevadas, pero no puedes acceder a ellas antes de declararlas. Esto genera un ReferenceError.javascript const obj = new MiClase(); // ReferenceError class MiClase {}
-
Variables con
-
Resumen:
-
var
→ Se eleva con valorundefined
. -
let
yconst
→ Se elevan, pero están en la TDZ. - Declaraciones de funciones → Se elevan completas.
- Clases → Se elevan, pero no se pueden usar antes de declararlas.
-
¿Te gustaría añadir más ejemplos o explicaciones? 😊
¿Qué es una IIFE (Immediately Invoked Function Expression) en JavaScript?
-
Definición:
Una IIFE es una función en JavaScript que se declara y se ejecuta inmediatamente después de su definición. -
Sintaxis:
Se envuelve la función en paréntesis para convertirla en una expresión, seguida de paréntesis adicionales para ejecutarla:javascript (function () { // Código aquí })();
-
Ejemplo básico:
javascript (function () { console.log("Esto es una IIFE"); })(); // Salida: Esto es una IIFE
-
Con parámetros:
Puedes pasar argumentos a una IIFE:javascript (function (nombre) { console.log(`Hola, ${nombre}`); })("Juan"); // Salida: Hola, Juan
-
¿Para qué se usa?
- Crear un ámbito privado y evitar contaminar el ámbito global.
- Encapsular variables y protegerlas de accesos externos.
- Ejecutar código una sola vez, como inicializadores.
-
Ejemplo práctico:
Crear una variable privada:
```javascript
const contador = (function () {
let count = 0;
return {
incrementar: function () {
count++;
console.log(count);
},
reiniciar: function () {
count = 0;
console.log(“Reiniciado”);
},
};
})();contador.incrementar(); // 1
contador.incrementar(); // 2
contador.reiniciar(); // Reiniciado
```
¿Cómo funciona el método reduce() en arrays en JavaScript?
-
Descripción:
El métodoreduce()
ejecuta una función de callback sobre cada elemento de un array, acumulando un único valor final. -
Sintaxis:
javascript array.reduce(callback(acumulador, elemento, indice, array), valorInicial);
-
callback: Función que se ejecuta en cada elemento del array.
- acumulador: El valor acumulado (resultado parcial).
- elemento: El elemento actual del array.
- indice (opcional): Índice del elemento actual.
- array (opcional): El array sobre el que se está iterando.
- valorInicial (opcional): Valor inicial del acumulador. Si no se especifica, se usa el primer elemento del array.
-
callback: Función que se ejecuta en cada elemento del array.
-
Ejemplo básico:
Sumar todos los números de un array:javascript const numeros = [1, 2, 3, 4]; const suma = numeros.reduce((acumulador, num) => acumulador + num, 0); console.log(suma); // 10
-
Ejemplo con objetos:
Calcular el total de precios:
```javascript
const productos = [
{ nombre: “Laptop”, precio: 1000 },
{ nombre: “Mouse”, precio: 20 },
{ nombre: “Teclado”, precio: 50 },
];const total = productos.reduce((acumulador, producto) => acumulador + producto.precio, 0);
console.log(total); // 1070
``` -
Ejemplo sin
valorInicial
:
Si no defines un valor inicial, el primer elemento se usa como acumulador y la iteración comienza desde el segundo elemento:javascript const numeros = [1, 2, 3, 4]; const suma = numeros.reduce((acumulador, num) => acumulador + num); console.log(suma); // 10
-
Notas importantes:
- Es útil para operaciones acumulativas como sumar, concatenar o transformar estructuras de datos.
-
reduce()
no modifica el array original.
Explica qué es el spread operator y proporciona un ejemplo.
Aquí tienes la tarjeta sobre el spread operator:
-
¿Qué es el spread operator?
El spread operator (...
) es una sintaxis de JavaScript que permite expandir o descomponer elementos de un iterable (como un array o un objeto) en elementos individuales. -
Usos comunes del spread operator:
- En arrays: Expande los elementos de un array dentro de otro array o en funciones.
- En objetos: Copia las propiedades de un objeto a otro.
-
Sintaxis:
javascript const nuevoArray = [...array]; const nuevoObjeto = { ...objeto };
Ejemplos:
-
En arrays:
- Unir dos arrays:
javascript const numeros = [1, 2, 3]; const masNumeros = [4, 5, 6]; const todosNumeros = [...numeros, ...masNumeros]; console.log(todosNumeros); // [1, 2, 3, 4, 5, 6]
- Copiar un array:
javascript const original = [1, 2, 3]; const copia = [...original]; console.log(copia); // [1, 2, 3]
- Unir dos arrays:
-
En objetos:
- Copiar un objeto:
javascript const persona = { nombre: "Juan", edad: 30 }; const personaCopiada = { ...persona }; console.log(personaCopiada); // { nombre: "Juan", edad: 30 }
- Modificar un objeto al copiarlo:
javascript const persona = { nombre: "Juan", edad: 30 }; const personaActualizada = { ...persona, edad: 31 }; console.log(personaActualizada); // { nombre: "Juan", edad: 31 }
- Copiar un objeto:
-
En funciones (pasar elementos de un array como argumentos):
javascript const numeros = [1, 2, 3]; const suma = (a, b, c) => a + b + c; console.log(suma(...numeros)); // 6
Resumen:
El spread operator ...
es muy útil para copiar, combinar o pasar elementos de arrays u objetos de manera eficiente.
¿Te gustaría algún ejemplo adicional o explicación más detallada? 😊
Copia superficial (Shallow Copy)
Cuando usas el spread operator, se copian las propiedades primitivas (como string, number, boolean, etc.), pero si alguna propiedad es un objeto o un array, solo se copia la referencia a ese objeto, no el objeto en sí
Copia profunda (Deep Copy):
Si deseas hacer una copia completa (profunda) del objeto, donde también se copien los objetos o arrays internos, necesitarías usar una técnica adicional, como la serialización JSON o una función personalizada.
JSON.parse(JSON.stringify()): Fácil y rápido, pero limitado a tipos serializables y sin referencias circulares.
Recursión manual: Muy flexible y personalizable.
Lodash cloneDeep(): Completo y optimizado para casos complejos.
structuredClone(): Solución nativa en navegadores modernos, eficiente y compatible con tipos complejos.
¿Cómo implementarías una función para clonar objetos en JavaScript?
Para clonar un objeto en JavaScript, existen varias formas dependiendo de si quieres una copia superficial o una copia profunda. A continuación se presentan ambas implementaciones:
- Copia Superficial:
Una copia superficial se puede hacer utilizando el spread operator (...
) o Object.assign()
. Ambos crean una nueva referencia para el objeto, pero las propiedades de tipo objeto siguen apuntando a la misma referencia en memoria.
Usando el spread operator:
```javascript
function clonarObjetoSuperficial(obj) {
return { …obj };
}
const original = { nombre: “Juan”, direccion: { ciudad: “Madrid” } };
const clon = clonarObjetoSuperficial(original);
clon.direccion.ciudad = “Barcelona”;
console.log(original.direccion.ciudad); // “Barcelona” (se afecta el objeto original)
console.log(clon.direccion.ciudad); // “Barcelona”
~~~
Usando Object.assign()
:
```javascript
function clonarObjetoSuperficial(obj) {
return Object.assign({}, obj);
}
const original = { nombre: “Juan”, direccion: { ciudad: “Madrid” } };
const clon = clonarObjetoSuperficial(original);
clon.direccion.ciudad = “Barcelona”;
console.log(original.direccion.ciudad); // “Barcelona” (se afecta el objeto original)
console.log(clon.direccion.ciudad); // “Barcelona”
~~~
- Copia Profunda:
Para hacer una copia profunda, donde las propiedades internas (objetos, arrays) también se copian de forma independiente, puedes utilizar métodos como la serialización JSON.parse()
/JSON.stringify()
, recursión manual o librerías externas.
Usando JSON.parse()
y JSON.stringify()
:
```javascript
function clonarObjetoProfundo(obj) {
return JSON.parse(JSON.stringify(obj));
}
const original = { nombre: “Juan”, direccion: { ciudad: “Madrid” } };
const clon = clonarObjetoProfundo(original);
clon.direccion.ciudad = “Barcelona”;
console.log(original.direccion.ciudad); // “Madrid” (el objeto original no se afecta)
console.log(clon.direccion.ciudad); // “Barcelona”
~~~
Usando recursión manual (función personalizada):
```javascript
function clonarObjetoProfundo(obj) {
if (obj === null || typeof obj !== ‘object’) {
return obj; // Si no es un objeto, devuelve el valor.
}
const clon = Array.isArray(obj) ? [] : {}; // Crea un array o un objeto según corresponda.
for (const clave in obj) {
if (obj.hasOwnProperty(clave)) {
clon[clave] = clonarObjetoProfundo(obj[clave]); // Recursión
}
}
return clon;
}
const original = { nombre: “Juan”, direccion: { ciudad: “Madrid” } };
const clon = clonarObjetoProfundo(original);
clon.direccion.ciudad = “Barcelona”;
console.log(original.direccion.ciudad); // “Madrid” (el objeto original no se afecta)
console.log(clon.direccion.ciudad); // “Barcelona”
~~~
Resumen:
-
Copia superficial: Utiliza el spread operator (
{...obj}
) oObject.assign()
para clonar el objeto, pero solo las referencias de los objetos internos son copiadas. -
Copia profunda: Utiliza
JSON.parse()
/JSON.stringify()
para objetos simples, o una función recursiva para manejar objetos más complejos, garantizando que los objetos internos también se clonen.
¿Qué es la programación asíncrona y cómo se maneja en JavaScript?
Programación asíncrona:
Es un enfoque en el que las operaciones no bloquean la ejecución del código. Permite que otras tareas se realicen mientras una operación, como una solicitud a un servidor o la lectura de un archivo, está en progreso.
-
Cómo se maneja en JavaScript:
- Callbacks: Funciones que se ejecutan cuando una tarea asíncrona termina.
- Promises: Objetos que representan el resultado de una operación asíncrona. Pueden estar en tres estados: pendiente, resuelta o rechazada.
- Async/Await: Sintaxis que permite escribir código asíncrono de manera más fácil y similar al código síncrono.
Ejemplo con async/await
:
```javascript
async function obtenerDatos() {
let respuesta = await fetch(‘https://api.example.com/datos’);
let datos = await respuesta.json();
console.log(datos);
}
obtenerDatos();
~~~
Explica qué son los callbacks, las promesas y el async/await en JavaScript.
-
Callbacks:
Un callback es una función que se pasa como argumento a otra función y se ejecuta cuando la tarea asíncrona termina. Los callbacks pueden ser difíciles de manejar si se anidan varios, lo que se conoce como “callback hell”.Ejemplo de callback:
```javascript
function tareaAsincrona(callback) {
setTimeout(() => {
console.log(“Tarea completada”);
callback(); // Llamada al callback
}, 1000);
}tareaAsincrona(() => {
console.log(“Callback ejecutado”);
});
``` -
Promesas:
Una promesa es un objeto que representa el resultado de una operación asíncrona. Tiene tres estados posibles:- Pendiente: La operación aún no ha terminado.
- Resuelta (Fulfilled): La operación se completó correctamente.
- Rechazada (Rejected): La operación falló.
.then()
para el resultado exitoso y.catch()
para errores.Ejemplo de promesa:
```javascript
function tareaAsincrona() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let exito = true;
if (exito) {
resolve(“Tarea completada”);
} else {
reject(“Error en la tarea”);
}
}, 1000);
});
}tareaAsincrona()
.then(result => console.log(result))
.catch(error => console.log(error));
``` -
Async/Await:
async/await
es una sintaxis más moderna para manejar operaciones asíncronas.async
marca una función como asíncrona y permite el uso deawait
dentro de esa función.await
espera a que una promesa se resuelva antes de continuar con la ejecución.Ejemplo con async/await:
```javascript
async function tareaAsincrona() {
let resultado = await new Promise((resolve, reject) => {
setTimeout(() => resolve(“Tarea completada”), 1000);
});
console.log(resultado); // Espera a que la promesa se resuelva
}tareaAsincrona();
```
Resumen:
- Callbacks: Funciones pasadas como argumentos y ejecutadas cuando se completa la tarea asíncrona.
- Promesas: Representan el resultado de una operación asíncrona, con tres estados: pendiente, resuelta o rechazada.
- Async/Await: Sintaxis que permite trabajar con promesas de manera más clara, evitando el anidamiento de callbacks.
¿Qué son los módulos de ES6 y cómo se configuran?
Los módulos de ES6 (ECMAScript 6) permiten dividir el código JavaScript en archivos separados, lo que facilita la organización, reutilización y mantenimiento del código. Los módulos permiten importar y exportar funciones, objetos y valores entre diferentes archivos.
Principales características de los módulos de ES6:
- Los módulos son escritos en archivos separados (con extensión .js
o .mjs
).
- Usan las palabras clave import
y export
para intercambiar funcionalidades entre archivos.
- Los módulos son estáticos, lo que significa que las dependencias se resuelven en tiempo de compilación.
Cómo se configuran:
- Exportar elementos de un módulo:
Puedes exportar variables, funciones o clases de un archivo para que puedan ser importadas por otros módulos.
-
Exportación nombrada: Exportas múltiples elementos de un módulo de forma individual.
javascript // archivo1.js export const nombre = "Juan"; export function saludar() { console.log("Hola, " + nombre); }
-
Exportación por defecto: Exportas un único valor o función como la exportación principal.
javascript // archivo2.js export default function() { console.log("Esta es una función por defecto"); }
- Importar elementos de un módulo:
-
Importación nombrada: Importas elementos específicos desde un módulo.
javascript // archivo3.js import { nombre, saludar } from './archivo1.js'; saludar(); // Salida: "Hola, Juan"
-
Importación por defecto: Importas el valor o función exportada por defecto.
javascript // archivo4.js import saludarDefault from './archivo2.js'; saludarDefault(); // Salida: "Esta es una función por defecto"
- Usar módulos en el navegador o Node.js:
- En el navegador, debes usar el atributo
type="module"
en la etiqueta<script>
:html <script type="module" src="app.js"></script>
- En Node.js:
- Desde Node.js versión 12, puedes usar módulos de ES6 de manera nativa con el archivo
.mjs
o configurando"type": "module"
en el archivopackage.json
.
- Desde Node.js versión 12, puedes usar módulos de ES6 de manera nativa con el archivo
Resumen:
- Módulos de ES6: Son archivos de JavaScript que permiten exportar e importar funcionalidades para mejorar la organización del código.
- Exportación: Se usa export
para compartir elementos (nombrados o por defecto).
- Importación: Se usa import
para traer esos elementos en otros archivos.
¿Cómo implementas un temporizador con setTimeout y setInterval?
setTimeout() ejecuta una función después de un tiempo específico (en milisegundos). Se utiliza para ejecutar una acción una sola vez después de un retraso.
let temporizador = setTimeout(() => {
console.log(“Esto no se ejecutará”);
}, 3000);
// Cancelar el temporizador antes de que se ejecute
clearTimeout(temporizador);
setInterval() ejecuta una función repetidamente, con un intervalo de tiempo entre cada ejecución.
¿Cuál es la diferencia entre call, apply y bind en JavaScript?
call()
:
- call()
permite invocar una función con un valor específico para this
y los argumentos pasados de manera individual.
- La sintaxis es:
func.call(thisValue, arg1, arg2, ...)
Ejemplo:
```javascript
function saludar() {
console.log(Hola, mi nombre es ${this.nombre}
);
}
const persona = { nombre: ‘Juan’ };
saludar.call(persona); // “Hola, mi nombre es Juan”
~~~
apply()
:
- apply()
es similar a call
, pero los argumentos se pasan como un array o array-like.
- La sintaxis es:
func.apply(thisValue, [arg1, arg2, ...])
Ejemplo:
```javascript
function saludar(mensaje) {
console.log(${mensaje}, mi nombre es ${this.nombre}
);
}
const persona = { nombre: ‘Juan’ };
saludar.apply(persona, [‘Hola’]); // “Hola, mi nombre es Juan”
~~~
bind()
:
- bind()
crea una nueva función con un this
específico y, opcionalmente, algunos argumentos predefinidos. Esta nueva función puede ser invocada más tarde.
- La sintaxis es:
const nuevaFuncion = func.bind(thisValue, arg1, arg2, ...)
Ejemplo:
```javascript
function saludar(mensaje) {
console.log(${mensaje}, mi nombre es ${this.nombre}
);
}
const persona = { nombre: ‘Juan’ };
const saludarJuan = saludar.bind(persona, ‘Hola’);
saludarJuan(); // “Hola, mi nombre es Juan”
~~~
Resumen:
- call()
: Llama a una función inmediatamente con un valor de this
específico y argumentos individuales.
- apply()
: Llama a una función inmediatamente con un valor de this
específico y argumentos como un array.
- bind()
: Crea una nueva función con un valor de this
específico y argumentos predefinidos que se puede ejecutar más tarde.
¿Cómo manejarías un array de promesas para que todas se resuelvan secuecialmente en lugar de en paralelo?
Para manejar un array de promesas y asegurarte de que se resuelvan secundariamente, puedes usar una estructura de control como un bucle for
junto con async/await
para esperar la resolución de cada promesa antes de continuar con la siguiente.
Forma de hacerlo secuencialmente:
Usando async/await
con un bucle for
:
```javascript
async function resolverSecuencialmente(promesas) {
for (let i = 0; i < promesas.length; i++) {
// Espera a que la promesa se resuelva antes de continuar con la siguiente
await promesas[i];
console.log(Promesa ${i + 1} resuelta
);
}
}
const promesas = [
new Promise(resolve => setTimeout(() => resolve(‘Primera promesa’), 1000)),
new Promise(resolve => setTimeout(() => resolve(‘Segunda promesa’), 1000)),
new Promise(resolve => setTimeout(() => resolve(‘Tercera promesa’), 1000)),
];
resolverSecuencialmente(promesas);
~~~
En este ejemplo, las promesas se ejecutan una por una. La siguiente promesa no se inicia hasta que la anterior se haya resuelto.
Alternativa con reduce()
y async/await
:
También puedes usar reduce()
para encadenar promesas de forma secuencial:
```javascript
async function resolverConReduce(promesas) {
await promesas.reduce(async (acumulador, promesa) => {
await acumulador; // Espera la promesa acumulada
await promesa; // Espera la siguiente promesa
console.log(“Promesa resuelta”);
}, Promise.resolve()); // Inicia con una promesa resuelta vacía
}
resolverConReduce(promesas);
~~~
Resumen:
- Para ejecutar promesas secuencialmente, puedes usar async/await
con un bucle for
o el método reduce()
.
- await
garantiza que se espere la resolución de una promesa antes de pasar a la siguiente.
¿Qué es el patrón “Revealing Module” en JavaScript?
Patrón “Revealing Module”:
El patrón “Revealing Module” es una técnica de diseño en JavaScript que busca encapsular y organizar el código dentro de un módulo. Este patrón se utiliza para exponer solo las funcionalidades necesarias, ocultando el resto de los detalles internos. A diferencia de otros patrones, el patrón “Revealing Module” enfatiza la exposición de solo lo necesario mediante el uso de funciones o variables.
Características del patrón “Revealing Module”:
- Se utiliza para encapsular el estado y el comportamiento dentro de un módulo.
- Expone explícitamente solo aquellas funciones o variables que deben ser accesibles desde fuera del módulo.
- El código interno del módulo está oculto para evitar conflictos de nombres y mantener el ámbito limpio.
Cómo funciona:
1. Crear un módulo utilizando una función autoejecutable (IIFE).
2. Definir variables y funciones internas dentro de la IIFE, que no son accesibles desde fuera del módulo.
3. Devolver un objeto que expone las funciones necesarias (esto puede ser la revelación).
Ejemplo de “Revealing Module”:
```javascript
const MiModulo = (function() {
// Variables privadas
let contador = 0;
// Funciones privadas
function incrementar() {
contador++;
}
// Función pública
function obtenerContador() {
return contador;
}
// Se revelan solo las funciones necesarias
return {
incrementar: incrementar,
obtenerContador: obtenerContador
};
})();
// Uso del módulo
MiModulo.incrementar();
console.log(MiModulo.obtenerContador()); // Salida: 1
~~~
Ventajas del patrón “Revealing Module”:
- Encapsulamiento: Protege el estado interno y las funciones que no deberían ser accesibles fuera del módulo.
- Organización: Facilita la gestión del código y la segregación de responsabilidades en módulos específicos.
- Mejor mantenimiento: Evita la contaminación del espacio global y mejora la estructura del código.
Resumen:
El patrón “Revealing Module” ayuda a organizar el código de forma más modular, ocultando detalles internos y exponiendo solo lo necesario mediante el retorno de un objeto con las funciones públicas. Esto permite mantener un buen encapsulamiento y una estructura clara.
¿Cómo implementarías un singleton en JavaScript?
Aquí tienes la tarjeta sobre cómo implementar un singleton en JavaScript:
Pregunta (Front):
¿Cómo implementarías un singleton en JavaScript?
Respuesta (Back):
Patrón Singleton:
El patrón Singleton asegura que una clase o módulo tenga una única instancia en todo el sistema y proporciona un punto de acceso global a esa instancia. Este patrón es útil cuando necesitas control centralizado, como en el manejo de configuraciones o conexiones a bases de datos.
Cómo Implementarlo:
-
Usando una función autoejecutable (IIFE):
Podemos utilizar una función autoejecutable (IIFE) para crear un objeto que contenga una única instancia y que la devuelva si ya existe, o cree una nueva si no la tiene. -
Usando una clase (ES6):
En lugar de usar una función, podemos emplear una clase y almacenar la instancia como un campo estático de la clase.
```javascript
class Singleton {
constructor() {
if (Singleton.instancia) {
return Singleton.instancia;
}
this.valor = “Soy un singleton”;
Singleton.instancia = this;
}
obtenerValor() {
return this.valor;
}
}
// Uso del singleton
const instancia1 = new Singleton();
const instancia2 = new Singleton();
console.log(instancia1 === instancia2); // true, ambas son la misma instancia
~~~
Ventajas del patrón Singleton:
- Instancia única: Garantiza que solo haya una instancia de la clase en todo el sistema.
- Acceso global: Ofrece un único punto de acceso a la instancia.
- Control centralizado: Ideal para administrar recursos compartidos, como configuraciones o conexiones.
Resumen:
El patrón Singleton garantiza que una clase tenga solo una única instancia en la aplicación y que esa instancia esté disponible globalmente. Puedes implementarlo utilizando una IIFE o una clase con un campo estático para almacenar la instancia.
¿Qué es un Symbol en JavaScript y cuándo lo utilizarías?
- Un Symbol es un tipo de dato primitivo único e inmutable.
- Se utiliza principalmente como claves de propiedades en objetos para evitar conflictos.
- Son útiles cuando necesitas identificadores únicos que no sean susceptibles a sobrescritura o acceso accidental.
Cuando necesitas valores que sean constantes, pero que no sean accesibles a través de otras propiedades del objeto o no puedan ser modificados accidentalmente, como claves internas o privadas.
Cuando desarrollas librerías o módulos, los Symbols pueden evitar que los usuarios modifiquen internamente las claves del objeto, ya que son inaccesibles fuera del módulo.
¿Qué es el strict mode en JavaScript y cómo se activa?
El strict mode (modo estricto) es una forma de ejecutar código en JavaScript con reglas más estrictas y con restricciones adicionales que ayudan a detectar errores en el código de forma temprana y mejorar la seguridad y el rendimiento.
Al usar el strict mode, JavaScript aplica ciertas restricciones y prohibe ciertas prácticas que son potencialmente problemáticas o propensas a errores. Estas restricciones facilitan el desarrollo de código más robusto y limpio.
Características del Strict Mode:
1. No permite declarar variables sin let
, const
o var
:
En modo estricto, no puedes usar variables no declaradas, lo que ayuda a evitar errores de referencia.
javascript 'use strict'; a = 10; // Error: a is not defined
-
No permite eliminar variables o funciones:
En strict mode, no puedes usardelete
en variables o funciones.javascript 'use strict'; var x = 10; delete x; // Error: Cannot delete variable 'x'
-
Evita la duplicación de parámetros en funciones:
En modo estricto, las funciones no pueden tener parámetros duplicados.javascript 'use strict'; function suma(a, a) { // Error: Duplicate parameter name not allowed in this context return a + a; }
-
this
en funciones no estrictas:
En el modo estricto, si se invoca una función sin un objeto, el valor dethis
esundefined
, en lugar de ser el objeto global (window
en navegadores).javascript 'use strict'; function saludar() { console.log(this); // undefined } saludar();
-
Prohibición de uso de palabras reservadas:
En strict mode, no se puede usar palabras reservadas que podrían ser reservadas en futuras versiones de JavaScript, comolet
,class
,const
, etc.
¿Cómo activar el Strict Mode?
-
Activarlo a nivel de archivo:
Puedes activar el modo estricto en todo un archivo JavaScript añadiendo"use strict";
al principio del archivo.
```javascript
‘use strict’;
var x = 10;
~~~
-
Activarlo a nivel de función:
También puedes activar el modo estricto dentro de una función para que se aplique solo dentro de esa función.
```javascript
function miFuncion() {
‘use strict’;
var x = 20;
}
~~~
Ventajas del Strict Mode:
- Mayor seguridad: Ayuda a evitar errores comunes, como el uso de variables no declaradas.
- Mejora el rendimiento: Algunos motores de JavaScript pueden optimizar el código cuando está en strict mode.
- Detecta errores con mayor facilidad: Al prohibir ciertas prácticas, los errores son más fáciles de detectar.
Resumen:
El strict mode es una forma de ejecutar el código JavaScript con reglas más estrictas para detectar errores y mejorar la seguridad y el rendimiento. Se activa con "use strict";
al principio del archivo o de una función.
¿Cuáles son las diferencias entre “prototypal inheritance” y “classical inheritance”?
Prototypal Inheritance:
- En JavaScript, los objetos heredan directamente de otros objetos.
- Prototipos: Cada objeto tiene un prototipo, y puede acceder a las propiedades y métodos de este prototipo.
- Dinámico: Puedes modificar el prototipo de un objeto en cualquier momento.
```javascript
const animal = {
hablar: function() {
console.log(“Animal habla”);
}
};
const perro = Object.create(animal);
perro.hablar(); // “Animal habla”
~~~
Classical Inheritance:
- En lenguajes como Java o C++, las clases heredan de otras clases.
- Utiliza clases y constructores para crear objetos, y la herencia se realiza a través de la creación de subclases.
- Estática: La relación de herencia está definida en el momento de la creación de la clase.
Ejemplo:
```javascript
class Animal {
hablar() {
console.log(“Animal habla”);
}
}
class Perro extends Animal {
// Hereda el método hablar()
}
const perro = new Perro();
perro.hablar(); // “Animal habla”
~~~
Resumen:
- Prototypal inheritance: La herencia se da a través de objetos y sus prototipos.
- Classical inheritance: La herencia se realiza a través de clases y subclases.
¿Qué es un WeakMap y un WeakSet?
WeakMap:
- Un WeakMap es un tipo de colección de pares clave-valor donde las claves deben ser objetos y las valores pueden ser cualquier tipo.
- A diferencia de los Map, las claves de un WeakMap no previenen que los objetos sean recolectados por el garbage collector (recolector de basura). Si un objeto clave ya no tiene referencias externas, puede ser garbage collected.
Características:
- Las claves deben ser objetos.
- Las valores pueden ser cualquier tipo.
- Las claves no impiden la recolección de basura.
Ejemplo:
```javascript
const weakmap = new WeakMap();
let obj = {};
weakmap.set(obj, ‘valor’);
console.log(weakmap.get(obj)); // “valor”
obj = null; // La referencia a obj se elimina, y el WeakMap puede recolectar la clave
~~~
WeakSet:
- Un WeakSet es una colección de valores únicos, similar a un Set, pero las únicas referencias permitidas son objetos.
- Al igual que en WeakMap, los objetos dentro de un WeakSet no impiden que sean recolectados por el garbage collector.
Características:
- Solo permite objetos como valores.
- Los objetos pueden ser recolectados por el garbage collector.
Ejemplo:
```javascript
const weakset = new WeakSet();
let obj = {};
weakset.add(obj);
console.log(weakset.has(obj)); // true
obj = null; // El objeto se puede recolectar cuando ya no hay referencias
~~~
Resumen:
- WeakMap: Mapa con claves que son objetos, y valores que pueden ser cualquier tipo. Las claves no impiden la recolección de basura.
- WeakSet: Set que solo contiene objetos, y estos objetos pueden ser recolectados por el garbage collector.
¿Cómo funciona el método Object.freeze()?
¿Qué hace Object.freeze()?
El método Object.freeze()
en JavaScript se utiliza para congelar un objeto, es decir, previene que se puedan agregar, eliminar o modificar las propiedades del objeto. Después de aplicar Object.freeze()
, el objeto se convierte en inmutable en cuanto a su estructura, aunque no congela los objetos internos (profundamente).
Características:
1. Propiedades no pueden ser modificadas: No se pueden cambiar los valores de las propiedades.
2. No se pueden agregar o eliminar propiedades: No puedes agregar nuevas propiedades ni eliminar las existentes.
3. El objeto es inmutable en su nivel superficial: No congela las propiedades de objetos internos (si el valor de la propiedad es un objeto).
Ejemplo básico:
```javascript
const persona = {
nombre: ‘Juan’,
edad: 30
};
Object.freeze(persona);
persona.nombre = ‘Carlos’; // No tiene efecto
persona.pais = ‘España’; // No se agrega la propiedad
console.log(persona); // { nombre: ‘Juan’, edad: 30 }
~~~
¿Qué no hace?
- No congela profundamente los objetos dentro del objeto. Si una propiedad es un objeto, sus propiedades internas pueden ser modificadas.
```javascript
const persona = {
nombre: ‘Juan’,
direccion: {
ciudad: ‘Madrid’
}
};
Object.freeze(persona);
persona.direccion.ciudad = ‘Barcelona’; // Esto es posible
console.log(persona.direccion.ciudad); // “Barcelona”
~~~
Si quieres congelar también los objetos internos, necesitas usar un enfoque recursivo.
Resumen:
- Object.freeze()
hace que un objeto sea inmutable a nivel superficial.
- No se pueden agregar, eliminar o modificar propiedades.
- No congela las propiedades de los objetos internos (no es una congelación profunda).
¿Qué es el concepto de “lazy evaluation”?
La “lazy evaluation” (evaluación perezosa) es un concepto en programación en el que una expresión o cálculo no se evalúa hasta que se necesita. En lugar de evaluar los valores de manera inmediata, la evaluación se retrasa hasta que el valor sea realmente requerido en el flujo de ejecución del programa.
Este enfoque puede mejorar el rendimiento de un programa al evitar cálculos innecesarios y ahorrar recursos cuando no se necesitan todos los resultados posibles.
Cómo funciona:
- Los valores no se calculan de inmediato.
- Los resultados solo se calculan cuando el programa lo requiere, por ejemplo, cuando se accede a un valor o se utiliza en una operación.
- En lenguajes funcionales, las listas o colecciones pueden ser evaluadas perezosamente para evitar la creación de datos intermedios innecesarios.
Ejemplo con arrays en JavaScript:
```javascript
function generarNumeros() {
console.log(“Generando números…”);
return [1, 2, 3, 4, 5];
}
let numeros = generarNumeros(); // No se genera el array hasta que lo necesitemos
console.log(numeros[0]); // Aquí es cuando se evalúa y genera el array
~~~
Ventajas:
- Optimización de recursos: Se evitan cálculos innecesarios.
- Evaluación condicional: Permite que los cálculos se realicen solo si son necesarios para el resultado final.
Desventajas:
- Complejidad: El control de la ejecución diferida puede ser más difícil de gestionar y predecir.
- Efectos secundarios: Si no se controlan adecuadamente, pueden generar efectos secundarios inesperados al evaluar las expresiones más tarde.
Resumen:
La lazy evaluation es un enfoque donde los cálculos se retrasan hasta que son realmente necesarios, lo que puede mejorar el rendimiento evitando cálculos innecesarios.