Al final de la lección anterior quedó una pregunta en el aire: si un objeto puede guardar números, strings y arrays, ¿por qué no funciones? La respuesta es que sí puede, y eso cambia la forma de organizar el código. Una función guardada en una propiedad se llama método, y permite que el tablero de Taller Nómada no sea solo una bolsa de tareas, sino un objeto que sabe agregar una tarea, cambiarle el estado y calcular su propio resumen. Con los métodos aparece this, la palabra clave con peor fama de JavaScript: no porque sea complicada, sino porque casi todo el mundo la aprende con la regla equivocada. En esta lección la aprenderás con la regla correcta —this depende de cómo se llama la función, no de dónde se define—, verás las cuatro formas de invocación en una tabla, resolverás el problema clásico de perder this al pasar un método como callback y cerrarás por fin la deuda que dejó 03-02 sobre las funciones flecha.
Contenido
- De función suelta a método
- La sintaxis abreviada de métodos
- Qué es
thisy la regla fundamental - Las cuatro formas de invocación
call,applyybind- El objeto
tablerode Taller Nómada - El problema clásico: perder
thisen un callback - Las tres soluciones
- Por qué la función flecha no tiene
thispropio thisen bucles y funciones anidadas- Cuándo la flecha ayuda y cuándo estorba
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- De función suelta a método
Partimos del backlog de la lección anterior y de describirTarea(tarea, hoy). Ahora mismo, la función y los datos viven separados:
Nada impide meter la función dentro del objeto:
const tarea = {
id: 6,
titulo: 'Presupuesto de la carpintería',
responsable: 'Iván',
prioridad: 'alta',
estado: 'pendiente',
horasEstimadas: 5,
fechaLimite: '2026-09-05',
// Una propiedad cuyo valor es una función: eso es un MÉTODO
describir: function (hoy) {
return `[${this.id}] ${this.titulo} · ${this.responsable} · ${this.horasEstimadas} h`;
}
};
console.log(tarea.describir('2026-09-20'));
// [6] Presupuesto de la carpintería · Iván · 5 hDos cosas nuevas en ese bloque:
describires una propiedad como cualquier otra; lo único especial es que su valor es una función. Puedes comprobarlo:typeof tarea.describires'function', yObject.keys(tarea)la incluye.- Dentro del método aparece
this, que en esta llamada se refiere al propio objetotarea. Gracias a él, el método puede leer los datos de «su» objeto sin que se los pases como argumento.
La diferencia de vocabulario es simple:
| Término | Definición | Ejemplo |
|---|---|---|
| Función | Código con nombre que se invoca por sí mismo | pesoDePrioridad('alta') |
| Método | Función guardada en una propiedad de un objeto | tarea.describir('2026-09-20') |
| Receptor | El objeto que está a la izquierda del punto en la llamada | tarea |
- La sintaxis abreviada de métodos
Escribir describir: function (hoy) { ... } funciona, pero desde ES2015 existe una forma más corta y es la que se usa hoy:
const tablero = {
nombre: 'Taller Nómada',
// Sintaxis larga (todavía válida)
saludarLargo: function () {
return `Panel de ${this.nombre}`;
},
// Sintaxis abreviada: se quitan los dos puntos y la palabra function
saludar() {
return `Panel de ${this.nombre}`;
}
};
console.log(tablero.saludar()); // 'Panel de Taller Nómada'
console.log(tablero.saludarLargo()); // 'Panel de Taller Nómada'Las dos hacen lo mismo. La abreviada es más corta, más legible y es la que debes usar. Ojo con una tercera opción que parece equivalente pero no lo es:
const tableroRoto = {
nombre: 'Taller Nómada',
saludar: () => `Panel de ${this.nombre}` // ✗ función flecha
};
console.log(tableroRoto.saludar()); // 'Panel de undefined'Ese undefined es el tema del apartado 9. De momento, quédate con la regla operativa: para los métodos de un objeto literal, usa siempre la sintaxis abreviada, nunca una flecha.
- Qué es
this y la regla fundamental
this y la regla fundamentalthis es una palabra clave que, dentro de una función, apunta a un objeto. Cuál es ese objeto se decide en el momento de la llamada, no en el momento de escribir la función. Esta es la regla que hay que memorizar:
thisno depende de dónde se define la función, sino de CÓMO se la llama.
Suena abstracto hasta que ves una misma función comportarse de tres maneras distintas:
'use strict';
function quienSoy() {
return this;
}
const objetoA = { nombre: 'A', quienSoy };
const objetoB = { nombre: 'B', quienSoy };
console.log(objetoA.quienSoy().nombre); // 'A'
console.log(objetoB.quienSoy().nombre); // 'B'
console.log(quienSoy()); // undefined (en modo estricto)Es la misma función, definida una sola vez. Lo único que cambia es lo que hay a la izquierda del punto al llamarla. Con esa imagen mental, this deja de ser magia: es simplemente «el objeto sobre el que se ha invocado esto».
flowchart LR
A["objetoA.quienSoy()"] --> B["this = objetoA"]
C["objetoB.quienSoy()"] --> D["this = objetoB"]
E["quienSoy()"] --> F["this = undefined<br/>(modo estricto)"]
G["quienSoy.call(objetoA)"] --> H["this = objetoA"]
- Las cuatro formas de invocación
Toda la complejidad de this se reduce a esta tabla. Cuando dudes de a qué apunta this, busca en qué fila estás.
| Forma de invocación | Cómo se escribe | Valor de this |
|---|---|---|
| Llamada simple | funcion() |
undefined en modo estricto; el objeto global (window/globalThis) sin modo estricto |
| Como método | objeto.metodo() |
El objeto receptor (lo que está a la izquierda del punto) |
Con new |
new Constructor() |
El objeto nuevo que se crea |
Con call/apply/bind |
funcion.call(obj, ...) |
El objeto que tú indicas explícitamente |
Vamos una por una.
Llamada simple. Es lo que has hecho durante todo el Módulo 3. En modo estricto ('use strict', que llevas usando desde 01-04) this es undefined. Sin modo estricto sería el objeto global, y ese comportamiento silencioso es una fuente inagotable de bugs: por eso el modo estricto existe.
Como método. El caso normal: this es el receptor.
const equipo = {
coordinadora: 'Marta',
presentar() {
return `Coordina ${this.coordinadora}`;
}
};
console.log(equipo.presentar()); // 'Coordina Marta'Aquí conviene una advertencia: this es el objeto que está inmediatamente a la izquierda del punto en la llamada, no el objeto donde el método está escrito. Si mueves el método a otra variable, cambia todo (apartado 7).
Con new. Cuando llamas a una función con new, JavaScript crea un objeto vacío, hace que this apunte a él, ejecuta el cuerpo y devuelve ese objeto.
function Tarea(id, titulo) {
this.id = id;
this.titulo = titulo;
this.estado = 'pendiente';
}
const nueva = new Tarea(7, 'Revisar la guillotina');
console.log(nueva); // Tarea { id: 7, titulo: 'Revisar la guillotina', estado: 'pendiente' }Este es el mecanismo sobre el que se construyen los constructores, los prototipos y las clases, y se estudia a fondo en Prototipos y Herencia y Clases y POO. Aquí solo necesitas saber que existe y que es la tercera fila de la tabla.
Con call, apply o bind. Es el apartado siguiente.
call, apply y bind
call, apply y bindToda función de JavaScript trae de serie tres métodos que sirven para decidir this a mano.
function describir(prefijo, sufijo) {
return `${prefijo}${this.titulo} (${this.horasEstimadas} h)${sufijo}`;
}
const tarea3 = { titulo: 'Actualizar la web de reservas', horasEstimadas: 14 };
// call: this primero, luego los argumentos SUELTOS
console.log(describir.call(tarea3, '» ', ' ⏳'));
// » Actualizar la web de reservas (14 h) ⏳
// apply: this primero, luego los argumentos en un ARRAY
console.log(describir.apply(tarea3, ['» ', ' ⏳']));
// » Actualizar la web de reservas (14 h) ⏳
// bind: NO llama; devuelve una función NUEVA con this fijado para siempre
const describirTarea3 = describir.bind(tarea3);
console.log(describirTarea3('· ', ''));
// · Actualizar la web de reservas (14 h)Resumen comparativo:
| Método | ¿Ejecuta ya? | Cómo pasa los argumentos | Qué devuelve |
|---|---|---|---|
call(this, a, b) |
Sí | Sueltos | El resultado de la función |
apply(this, [a, b]) |
Sí | En un array | El resultado de la función |
bind(this, a) |
No | Sueltos (se pueden prefijar) | Una función nueva con this atado |
Un truco mnemotécnico: call = comas, apply = array, bind = bloquea (fija this y devuelve una función para más tarde). En la práctica moderna call y apply se usan poco —el spread de 04-07 sustituye casi todos los usos de apply—, pero bind sigue siendo esencial, como verás en el apartado 8.
Un detalle que sorprende: bind es definitivo. Una función ya atada no se puede volver a atar.
const atadaAMarta = describir.bind({ titulo: 'A', horasEstimadas: 1 });
const intento = atadaAMarta.bind({ titulo: 'B', horasEstimadas: 2 });
console.log(intento('', '')); // 'A (1 h)' ← el segundo bind no tiene efecto
- El objeto
tablero de Taller Nómada
tablero de Taller NómadaCon esto ya podemos construir la pieza central de la lección: un objeto que guarda el backlog y las operaciones sobre él. Marta lo describiría así: «el tablero es la lista de tareas más lo que se puede hacer con ella».
'use strict';
const HOY = '2026-09-20';
const PESOS = { alta: 3, media: 2, baja: 1 };
const MARCAS = { hecha: '✓', 'en-curso': '▸', pendiente: '○' };
const ESTADOS_VALIDOS = ['pendiente', 'en-curso', 'hecha'];
const tablero = {
nombre: 'Taller Nómada',
hoy: HOY,
tareas: [
{ id: 1, titulo: 'Rediseñar la sala polivalente', responsable: 'Iván', prioridad: 'alta', estado: 'en-curso', horasEstimadas: 12, fechaLimite: '2026-09-30' },
{ id: 2, titulo: 'Cartelería del taller de serigrafía', responsable: 'Marta', prioridad: 'media', estado: 'pendiente', horasEstimadas: 6, fechaLimite: '2026-10-15' },
{ id: 3, titulo: 'Actualizar la web de reservas', responsable: 'Lucía', prioridad: 'alta', estado: 'pendiente', horasEstimadas: 14, fechaLimite: '2026-10-02' },
{ id: 4, titulo: 'Inventario de tintas de serigrafía', responsable: 'Marta', prioridad: 'baja', estado: 'hecha', horasEstimadas: 3, fechaLimite: '2026-09-12' },
{ id: 5, titulo: 'Guía de encuadernación para residentes', responsable: 'Iván', prioridad: 'media', estado: 'en-curso', horasEstimadas: 8, fechaLimite: '2026-11-05' },
{ id: 6, titulo: 'Presupuesto de la carpintería', responsable: 'Iván', prioridad: 'alta', estado: 'pendiente', horasEstimadas: 5, fechaLimite: '2026-09-05' }
],
// ─── Métodos de consulta ────────────────────────────────────────
buscarPorId(id) {
for (const tarea of this.tareas) {
if (tarea.id === id) return tarea;
}
return null;
},
// ─── Métodos que modifican el tablero ───────────────────────────
agregar(tarea) {
if (this.buscarPorId(tarea.id) !== null) {
throw new Error(`Ya existe una tarea con id ${tarea.id}.`);
}
this.tareas.push(tarea);
return this; // devolver this permite encadenar llamadas
},
cambiarEstado(id, nuevoEstado) {
if (!ESTADOS_VALIDOS.includes(nuevoEstado)) {
throw new Error(`Estado no válido: ${nuevoEstado}`);
}
const tarea = this.buscarPorId(id);
if (tarea === null) {
throw new Error(`No hay ninguna tarea con id ${id}.`);
}
tarea.estado = nuevoEstado;
return this;
},
// ─── Método de agregación ───────────────────────────────────────
resumen() {
let abiertas = 0;
let horasAbiertas = 0;
let vencidas = 0;
let esfuerzo = 0;
for (const tarea of this.tareas) {
esfuerzo += (PESOS[tarea.prioridad] ?? 0) * tarea.horasEstimadas;
if (tarea.estado === 'hecha') continue;
abiertas++;
horasAbiertas += tarea.horasEstimadas;
if (tarea.fechaLimite < this.hoy) vencidas++;
}
return { total: this.tareas.length, abiertas, horasAbiertas, vencidas, esfuerzo };
},
linea(tarea) {
const marca = MARCAS[tarea.estado] ?? '?';
const aviso = tarea.fechaLimite < this.hoy && tarea.estado !== 'hecha' ? ' ⚠ VENCIDA' : '';
return `${marca} [${tarea.id}] ${tarea.titulo} · ${tarea.responsable} · ${tarea.horasEstimadas} h${aviso}`;
}
};
console.log(tablero.resumen());
// { total: 6, abiertas: 5, horasAbiertas: 45, vencidas: 1, esfuerzo: 124 }
console.log(tablero.linea(tablero.buscarPorId(6)));
// ○ [6] Presupuesto de la carpintería · Iván · 5 h ⚠ VENCIDALos números coinciden con el backlog canónico: 6 tareas, 5 abiertas, 45 h abiertas, 1 vencida y un esfuerzo ponderado de 124. Repasa tres decisiones de diseño de este objeto:
this.tareas, notareas. Dentro de un método, para llegar a las propiedades del propio objeto hay que pasar porthis. Escribirtareas.push(...)daríaReferenceError, porque no existe ninguna variable con ese nombre.- Los métodos se llaman entre sí con
this.agregarusathis.buscarPorId(...). Esto es lo que permite reutilizar lógica sin duplicarla. agregarycambiarEstadodevuelventhis. Es el patrón de interfaz fluida: como cada llamada devuelve el propio tablero, se pueden encadenar.
tablero
.agregar({ id: 7, titulo: 'Revisar la guillotina de encuadernación', responsable: 'Lucía',
prioridad: 'media', estado: 'pendiente', horasEstimadas: 2, fechaLimite: '2026-10-20' })
.cambiarEstado(2, 'en-curso')
.cambiarEstado(6, 'hecha');
console.log(tablero.resumen());
// { total: 7, abiertas: 5, horasAbiertas: 42, vencidas: 0, esfuerzo: 128 }Y los errores usan el throw new Error que aprendiste en 02-05, de modo que quien llame puede envolverlo en try/catch:
try {
tablero.cambiarEstado(99, 'hecha');
} catch (error) {
console.log(`No se pudo actualizar: ${error.message}`);
}
// No se pudo actualizar: No hay ninguna tarea con id 99.
- El problema clásico: perder
this en un callback
this en un callbackAquí llega el momento en que this muerde. Recupera la regla: this depende de cómo se llama la función. Si extraes un método de su objeto, la llamada deja de tener receptor.
'use strict';
const resumir = tablero.resumen; // extraemos el método a una variable
console.log(resumir());
// ✗ TypeError: Cannot read properties of undefined (reading 'tareas')¿Por qué? Porque resumir() es una llamada simple (primera fila de la tabla): this vale undefined, y this.tareas revienta. La función es la misma; la llamada, no.
El caso realmente frecuente es más sutil, porque no parece que estés extrayendo nada: ocurre al pasar un método como callback, algo que harás constantemente con los eventos del Módulo 6 y con los métodos de array del resto de este módulo.
const notificador = {
destinatario: 'Marta',
avisar(titulo) {
console.log(`Aviso para ${this.destinatario}: ${titulo}`);
}
};
notificador.avisar('Presupuesto de la carpintería');
// Aviso para Marta: Presupuesto de la carpintería ✓
const titulos = ['Presupuesto de la carpintería', 'Actualizar la web de reservas'];
titulos.forEach(notificador.avisar);
// ✗ TypeError: Cannot read properties of undefined (reading 'destinatario')Lo que ocurre es que titulos.forEach(notificador.avisar) no pasa el método, pasa la función que había dentro. Cuando forEach la invoca, lo hace como llamada simple, sin ningún receptor:
flowchart TD
A["notificador.avisar('x')"] --> B["Llamada CON receptor<br/>this = notificador ✓"]
C["const f = notificador.avisar<br/>f('x')"] --> D["Llamada SIN receptor<br/>this = undefined ✗"]
E["forEach(notificador.avisar)"] --> D
La imagen mental correcta: el punto no forma parte de la función.
notificador.avisarsignifica «dame el valor guardado en la propiedadavisar». Ese valor es una función anónima que no recuerda de dónde salió. El vínculo connotificadorsolo existe durante la llamada con punto.
- Las tres soluciones
Hay tres formas de arreglarlo, y conviene conocer las tres porque te las encontrarás en código ajeno.
Solución 1: bind. Atas el método a su objeto y pasas la función atada.
titulos.forEach(notificador.avisar.bind(notificador));
// Aviso para Marta: Presupuesto de la carpintería
// Aviso para Marta: Actualizar la web de reservasSolución 2: una función flecha envolvente. Llamas al método con punto dentro de la flecha, de modo que la llamada sí tiene receptor.
Esta es la más legible y la que se ve más a menudo hoy. Fíjate en que la clave no es la flecha en sí, sino que la invocación conserva el punto: notificador.avisar(titulo).
Solución 3: un método flecha como campo del objeto. Se define la propiedad con una flecha que captura this del ámbito de alrededor.
function crearNotificador(destinatario) {
const obj = {
destinatario,
avisar: (titulo) => console.log(`Aviso para ${obj.destinatario}: ${titulo}`)
};
return obj;
}
const aviso = crearNotificador('Lucía');
titulos.forEach(aviso.avisar);
// Aviso para Lucía: Presupuesto de la carpintería
// Aviso para Lucía: Actualizar la web de reservasEsta tercera variante es la menos habitual con objetos literales (funciona mejor con clases, Módulo 5), pero ilustra el patrón que verás en muchos proyectos. Comparación:
| Solución | Ventajas | Inconvenientes |
|---|---|---|
bind |
Explícita; funciona con cualquier función | Verbosa; crea una función nueva cada vez que se ejecuta |
| Flecha envolvente | Corta, clara, permite reordenar argumentos | Un nivel más de anidamiento |
| Método flecha | El método «nunca se desata» | Cada objeto tiene su propia copia de la función; no se puede reutilizar con call |
Y hay una cuarta vía específica de algunos métodos de array: forEach, map, filter y some aceptan un segundo argumento con el this que quieres usar dentro del callback.
const tareasCortas = ['Pedir presupuestos', 'Visitar a dos proveedores'];
tareasCortas.forEach(notificador.avisar, notificador);
// Aviso para Marta: Pedir presupuestos
// Aviso para Marta: Visitar a dos proveedoresFunciona, pero se usa poco porque es fácil de pasar por alto al leer el código.
- Por qué la función flecha no tiene
this propio
this propioEn 03-02 dijimos que las funciones flecha se comportan de forma distinta con this y dejamos la explicación para más adelante. Ha llegado el momento.
Una función flecha no tiene su propio
this. Dentro de una flecha,thises exactamente el mismo que había en el ámbito donde la flecha fue escrita. Se resuelve como cualquier otra variable, siguiendo la cadena de ámbitos que estudiaste en 03-04.
Esto explica el fallo del apartado 2:
'use strict';
const tableroRoto = {
nombre: 'Taller Nómada',
saludar: () => `Panel de ${this.nombre}`
};
console.log(tableroRoto.saludar()); // 'Panel de undefined'La flecha se escribió fuera de cualquier función, en el ámbito de módulo. Ahí this no es el objeto que la contiene (un literal de objeto no crea ámbito), sino lo que hubiera en ese nivel: undefined en un módulo, o module.exports en Node, o el objeto global en un script clásico. En ninguno de esos casos hay una propiedad nombre.
La tabla que resume la diferencia:
Función normal (function, o método abreviado) |
Función flecha | |
|---|---|---|
this |
Se decide en la llamada (las cuatro formas) | Se hereda del ámbito donde se escribió |
¿Afecta call/apply/bind? |
Sí | No: se ignoran |
| ¿Sirve como método de objeto literal? | Sí | No |
¿Sirve como callback que necesita el this de fuera? |
No sin bind |
Sí, perfectamente |
¿Tiene arguments? |
Sí | No (usa ...rest, 03-03) |
¿Se puede usar con new? |
Sí | No |
Compruébalo:
const objeto = { valor: 42 };
const normal = function () { return this?.valor; };
const flecha = () => this?.valor;
console.log(normal.call(objeto)); // 42 ← call funciona
console.log(flecha.call(objeto)); // undefined ← call se ignora
this en bucles y funciones anidadas
this en bucles y funciones anidadasEl caso que más confunde a los principiantes: una función normal anidada dentro de un método. Como es una llamada simple, pierde el this del método.
'use strict';
const informe = {
nombre: 'Taller Nómada',
tareas: [{ titulo: 'Presupuesto de la carpintería' }, { titulo: 'Actualizar la web de reservas' }],
imprimirMal() {
this.tareas.forEach(function (tarea) {
console.log(`${this.nombre}: ${tarea.titulo}`); // ✗ this es undefined
});
}
};
// informe.imprimirMal();
// ✗ TypeError: Cannot read properties of undefined (reading 'nombre')imprimirMal sí tiene bien su this (se llamó con punto). Pero la función anónima que recibe forEach se invoca sin receptor, y su this no tiene nada que ver con el del método que la rodea. Antes de las flechas, esto se resolvía con un truco que verás en código antiguo:
imprimirConSelf() {
const self = this; // se guarda el this bueno en una variable normal
this.tareas.forEach(function (tarea) {
console.log(`${self.nombre}: ${tarea.titulo}`);
});
}Con una flecha, el truco ya no hace falta, porque la flecha hereda el this del método donde se escribió:
const informe = {
nombre: 'Taller Nómada',
tareas: [{ titulo: 'Presupuesto de la carpintería' }, { titulo: 'Actualizar la web de reservas' }],
imprimir() {
this.tareas.forEach((tarea) => {
console.log(`${this.nombre}: ${tarea.titulo}`); // ✓ this = informe
});
}
};
informe.imprimir();
// Taller Nómada: Presupuesto de la carpintería
// Taller Nómada: Actualizar la web de reservasUn for...of clásico no tiene este problema en absoluto, porque no introduce ninguna función nueva:
imprimirConFor() {
for (const tarea of this.tareas) {
console.log(`${this.nombre}: ${tarea.titulo}`); // ✓ mismo this del método
}
}Regla derivada, muy práctica: un bucle no cambia this; una función sí. Si dentro de un método aparece la palabra function, sospecha.
- Cuándo la flecha ayuda y cuándo estorba
Con todo lo anterior, la decisión es mecánica:
| Situación | Elige | Motivo |
|---|---|---|
| Método de un objeto literal | Método abreviado metodo() {} |
Necesita el receptor como this |
| Callback dentro de un método | Función flecha | Hereda el this correcto sin bind |
Callback que no usa this (map, filter, comparadores) |
Función flecha | Más corta; this es irrelevante |
| Manejador de evento que necesita el elemento del DOM | Función normal | El navegador pone el elemento en this (Módulo 6) |
Constructor o función que se usará con new |
Función normal | Las flechas no admiten new |
| Función suelta sin relación con ningún objeto | Cualquiera | this no participa |
Aplicado al tablero, la versión definitiva combina las dos: métodos abreviados por fuera, flechas por dentro.
const tableroFinal = {
nombre: 'Taller Nómada',
hoy: HOY,
tareas: tablero.tareas,
titulosVencidos() {
return this.tareas
.filter((t) => t.fechaLimite < this.hoy && t.estado !== 'hecha') // flecha: this = tableroFinal
.map((t) => t.titulo);
}
};
console.log(tableroFinal.titulosVencidos());
// [ 'Presupuesto de la carpintería' ](Los métodos filter y map que aparecen ahí se estudian en las lecciones 04-04 y 04-05; aquí solo interesan como ejemplo de callbacks dentro de un método.)
Errores Comunes y Consejos
1. Olvidar this. dentro de un método.
resumen() {
for (const t of tareas) { } // ✗ ReferenceError: tareas is not defined
for (const t of this.tareas) { } // ✓
}2. Usar una flecha como método de un objeto literal. saludar: () => this.nombre nunca verá el objeto. Usa saludar() { ... }.
3. Pasar un método como callback sin atarlo. array.forEach(obj.metodo) pierde this. Escribe array.forEach((x) => obj.metodo(x)).
4. Creer que bind modifica la función original. No: devuelve una nueva. obj.metodo.bind(obj); en una línea suelta no hace absolutamente nada; hay que guardar el resultado.
5. Llamar a bind dentro de un bucle o de un render. Cada bind crea una función distinta. En el Módulo 6 eso significa manejadores de evento que no se pueden quitar con removeEventListener porque la referencia ya no es la misma. Ata una vez y guarda el resultado.
6. Confiar en this sin modo estricto. Sin 'use strict', una llamada simple pone this en el objeto global y las asignaciones a this.algo crean variables globales silenciosamente. Este curso usa modo estricto desde 01-04 precisamente para que ese error se convierta en un TypeError visible.
Consejo profesional. Cuando no sepas a qué apunta this, no razones: imprímelo. Un console.log(this) en la primera línea del método resuelve la duda en dos segundos, y en el Módulo 8 aprenderás a inspeccionarlo con un punto de interrupción sin tocar el código.
Ejercicios
Ejercicio 1 — Métodos de una tarea. Escribe un objeto tareaWeb con los datos de la tarea 3 del backlog (id 3, «Actualizar la web de reservas», Lucía, alta, pendiente, 14 h, '2026-10-02') y añade tres métodos abreviados: esAlta() (devuelve true si la prioridad es 'alta'), estaVencida(hoy) (aplica la R10) y etiqueta() (devuelve un string '[3] Actualizar la web de reservas — Lucía'). Pruébalos con HOY = '2026-09-20'.
Ejercicio 2 — Cazar el this perdido. Este código falla. Explica por qué y arréglalo de dos formas distintas (una con bind y otra con función flecha), sin cambiar el método recordar.
'use strict';
const agenda = {
responsable: 'Iván',
recordar(titulo) {
console.log(`${this.responsable} debe: ${titulo}`);
}
};
const pendientes = ['Rediseñar la sala polivalente', 'Presupuesto de la carpintería'];
pendientes.forEach(agenda.recordar);Ejercicio 3 — Contador de horas por persona. Amplía el objeto tablero con un método horasDe(persona) que devuelva las horas abiertas (estado distinto de 'hecha') de esa persona, y un método informePorPersona() que devuelva un objeto con una clave por responsable y sus horas abiertas. Usa this y los métodos entre sí. Comprueba que tablero.horasDe('Iván') da 25 y que el informe cuadra con las 45 h abiertas totales.
Soluciones
Ejercicio 1
'use strict';
const HOY = '2026-09-20';
const tareaWeb = {
id: 3,
titulo: 'Actualizar la web de reservas',
responsable: 'Lucía',
prioridad: 'alta',
estado: 'pendiente',
horasEstimadas: 14,
fechaLimite: '2026-10-02',
esAlta() {
return this.prioridad === 'alta';
},
estaVencida(hoy) {
return this.fechaLimite < hoy && this.estado !== 'hecha';
},
etiqueta() {
return `[${this.id}] ${this.titulo} — ${this.responsable}`;
}
};
console.log(tareaWeb.esAlta()); // true
console.log(tareaWeb.estaVencida(HOY)); // false (vence el 2 de octubre)
console.log(tareaWeb.etiqueta()); // '[3] Actualizar la web de reservas — Lucía'Fíjate en que estaVencida recibe hoy como parámetro en lugar de leerlo de una constante global: es la misma decisión de diseño de 03-03, y sirve para poder probar la función con cualquier fecha.
Ejercicio 2
El fallo está en pendientes.forEach(agenda.recordar). Ahí no se pasa «el método de agenda», se pasa la función que hay guardada en la propiedad recordar. Cuando forEach la invoca lo hace sin receptor —una llamada simple—, así que en modo estricto this vale undefined y this.responsable lanza un TypeError.
// Solución A: bind
pendientes.forEach(agenda.recordar.bind(agenda));
// Solución B: flecha envolvente, que conserva el punto en la llamada
pendientes.forEach((titulo) => agenda.recordar(titulo));
// Ambas imprimen:
// Iván debe: Rediseñar la sala polivalente
// Iván debe: Presupuesto de la carpinteríaUna tercera opción válida, específica de forEach, es aprovechar su segundo parámetro: pendientes.forEach(agenda.recordar, agenda);.
Ejercicio 3
const tablero = {
nombre: 'Taller Nómada',
hoy: HOY,
tareas: [ /* las seis tareas del backlog */ ],
abiertas() {
const resultado = [];
for (const tarea of this.tareas) {
if (tarea.estado !== 'hecha') resultado.push(tarea);
}
return resultado;
},
horasDe(persona) {
let total = 0;
for (const tarea of this.abiertas()) { // reutiliza el método anterior
if (tarea.responsable === persona) total += tarea.horasEstimadas;
}
return total;
},
informePorPersona() {
const informe = {};
for (const tarea of this.abiertas()) {
const clave = tarea.responsable;
informe[clave] = (informe[clave] ?? 0) + tarea.horasEstimadas;
}
return informe;
}
};
console.log(tablero.horasDe('Iván')); // 25
console.log(tablero.horasDe('Marta')); // 6
console.log(tablero.horasDe('Lucía')); // 14
console.log(tablero.informePorPersona()); // { Iván: 25, Marta: 6, Lucía: 14 }Comprobación: 25 + 6 + 14 = 45 h abiertas, exactamente las del backlog canónico (las 3 h de «Inventario de tintas» no cuentan porque está hecha). Dos detalles del código: informe[clave] usa corchetes porque la clave está en una variable (04-01), y (informe[clave] ?? 0) resuelve la primera vez, cuando la clave todavía no existe y devolvería undefined. En 04-05 harás exactamente este mismo agrupamiento con reduce en cuatro líneas.
Conclusión
Un método no es más que una función guardada en una propiedad, y se escribe con la sintaxis abreviada metodo() {}. Lo que lo hace especial es this, y ahora tienes la única regla que necesitas: this depende de cómo se llama la función, no de dónde se escribió. Con las cuatro formas de invocación en la cabeza —llamada simple (undefined en modo estricto), como método (el receptor), con new (el objeto nuevo, que desarrollarás en el Módulo 5) y con call/apply/bind (el que tú digas)— cualquier this misterioso deja de serlo: basta con mirar la llamada.
Has construido el objeto tablero de Taller Nómada, con buscarPorId, agregar, cambiarEstado, resumen y linea, comprobando que los métodos se llaman entre sí a través de this, que devolver this permite encadenar operaciones y que los errores se lanzan con el throw new Error de 02-05. Has diagnosticado el problema clásico —perder this al pasar un método como callback— y tienes las tres soluciones con sus ventajas e inconvenientes. Y has cerrado la deuda de 03-02: la función flecha no tiene this propio, lo hereda del ámbito donde se escribió, lo que la hace inútil como método de un objeto literal y perfecta como callback dentro de un método.
Fíjate ahora en qué ha sido lo más incómodo de esta lección. No ha sido this: han sido los bucles. buscarPorId recorre a mano hasta encontrar; resumen acumula cuatro variables en un for; horasDe filtra con un if dentro de otro bucle. El objeto ya está bien modelado, pero la lista de tareas todavía se maneja con las herramientas del Módulo 2. Eso cambia a partir de la lección siguiente, Arrays: Conceptos Básicos y Métodos, donde estudiarás el array a fondo: cómo se crea, cómo se mide, y sobre todo la distinción que más disgustos evita en JavaScript —métodos que mutan el array frente a métodos que devuelven uno nuevo—, con las etiquetas de una tarea como banco de pruebas.
Curso de JavaScript: De Principiante a Avanzado
Módulo 1: Introducción a JavaScript
- ¿Qué es JavaScript?
- Configuración de tu Entorno de Desarrollo
- Tu Primer Programa en JavaScript
- Sintaxis y Conceptos Básicos de JavaScript
- Variables y Tipos de Datos
- Operadores Básicos
- Conversión de Tipos y Comparaciones
- El Proyecto del Curso: Nómada Tareas
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for, while, do-while
- Sentencias Switch
- Control del Flujo: break, continue y Bucles Anidados
- Manejo de Errores con try-catch
Módulo 3: Funciones
- Definición y Llamada de Funciones
- Expresiones de Función y Funciones Flecha
- Parámetros y Valores de Retorno
- Ámbito y Closures
- Hoisting y el Contexto de Ejecución
- Funciones de Orden Superior
- Recursividad
Módulo 4: Objetos y Arrays
- Introducción a los Objetos
- Métodos de Objeto y la Palabra Clave
this - Arrays: Conceptos Básicos y Métodos
- Iteración sobre Arrays
- Buscar, Ordenar y Agregar Datos: find, sort y reduce
- Desestructuración de Arrays
- Desestructuración de Objetos, Spread y Rest
- JSON y Copias de Objetos
Módulo 5: Objetos y Funciones Avanzadas
- Prototipos y Herencia
- Clases y Programación Orientada a Objetos
- Encapsulación: Getters, Setters y Campos Privados
- Módulos e Importación/Exportación
- JavaScript Asíncrono: Callbacks
- Promesas y Async/Await
- El Bucle de Eventos y la Cola de Microtareas
- Iteradores y Generadores
Módulo 6: El Modelo de Objetos del Documento (DOM)
- Introducción al DOM
- Selección y Manipulación de Elementos del DOM
- Manejo de Eventos
- Propagación, Delegación y Eventos Personalizados
- Creación y Eliminación de Elementos del DOM
- Renderizado de Listas y Plantillas HTML
- Manejo y Validación de Formularios
Módulo 7: APIs del Navegador y Temas Avanzados
- Almacenamiento Local y de Sesión
- Fetch API y AJAX
- Peticiones Robustas: Errores, Timeouts y AbortController
- WebSockets
- Service Workers y Aplicaciones Web Progresivas (PWAs)
- APIs del Navegador Esenciales
- Introducción a WebAssembly
Módulo 8: Pruebas y Depuración
- Depuración de JavaScript
- Calidad de Código: ESLint, Prettier y Convenciones
- Pruebas Unitarias con Jest
- Dobles de Prueba: Mocks, Stubs y Spies
- Pruebas de Integración
- Pruebas de Extremo a Extremo con Cypress
Módulo 9: Rendimiento y Optimización
- Medir Antes de Optimizar: DevTools y Web Vitals
- Optimización del Rendimiento de JavaScript
- Gestión de Memoria
- Manipulación Eficiente del DOM
- Carga Perezosa y División de Código
Módulo 10: Frameworks y Librerías de JavaScript
- Por Qué Existen los Frameworks
- Introducción a React
- Gestión de Estado con Redux
- Conceptos Básicos de Vue.js
- Conceptos Básicos de Angular
- Elegir el Framework Adecuado
