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

  1. De función suelta a método
  2. La sintaxis abreviada de métodos
  3. Qué es this y la regla fundamental
  4. Las cuatro formas de invocación
  5. call, apply y bind
  6. El objeto tablero de Taller Nómada
  7. El problema clásico: perder this en un callback
  8. Las tres soluciones
  9. Por qué la función flecha no tiene this propio
  10. this en bucles y funciones anidadas
  11. Cuándo la flecha ayuda y cuándo estorba
  12. Errores Comunes y Consejos
  13. Ejercicios
  14. Conclusión

  1. 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:

const tarea = backlog[0];
console.log(describirTarea(tarea, HOY));

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 h

Dos cosas nuevas en ese bloque:

  • describir es una propiedad como cualquier otra; lo único especial es que su valor es una función. Puedes comprobarlo: typeof tarea.describir es 'function', y Object.keys(tarea) la incluye.
  • Dentro del método aparece this, que en esta llamada se refiere al propio objeto tarea. 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

  1. 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.

  1. Qué es this y la regla fundamental

this 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:

this no 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"]

  1. 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.

'use strict';

function informar() {
  console.log(this);
}

informar();          // undefined

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.

  1. call, apply y bind

Toda 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) Sueltos El resultado de la función
apply(this, [a, b]) 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

  1. El objeto tablero de Taller Nómada

Con 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 ⚠ VENCIDA

Los 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:

  1. this.tareas, no tareas. Dentro de un método, para llegar a las propiedades del propio objeto hay que pasar por this. Escribir tareas.push(...) daría ReferenceError, porque no existe ninguna variable con ese nombre.
  2. Los métodos se llaman entre sí con this. agregar usa this.buscarPorId(...). Esto es lo que permite reutilizar lógica sin duplicarla.
  3. agregar y cambiarEstado devuelven this. 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.

  1. El problema clásico: perder this en un callback

Aquí 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.avisar significa «dame el valor guardado en la propiedad avisar». Ese valor es una función anónima que no recuerda de dónde salió. El vínculo con notificador solo existe durante la llamada con punto.

  1. 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 reservas

Solució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.

titulos.forEach((titulo) => notificador.avisar(titulo));

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 reservas

Esta 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 proveedores

Funciona, pero se usa poco porque es fácil de pasar por alto al leer el código.

  1. Por qué la función flecha no tiene this propio

En 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, this es 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? No: se ignoran
¿Sirve como método de objeto literal? No
¿Sirve como callback que necesita el this de fuera? No sin bind Sí, perfectamente
¿Tiene arguments? No (usa ...rest, 03-03)
¿Se puede usar con new? 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

  1. this en bucles y funciones anidadas

El 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 reservas

Un 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.

  1. 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ía

Una 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

Módulo 2: Estructuras de Control

Módulo 3: Funciones

Módulo 4: Objetos y Arrays

Módulo 5: Objetos y Funciones Avanzadas

Módulo 6: El Modelo de Objetos del Documento (DOM)

Módulo 7: APIs del Navegador y Temas Avanzados

Módulo 8: Pruebas y Depuración

Módulo 9: Rendimiento y Optimización

Módulo 10: Frameworks y Librerías de JavaScript

Módulo 11: Proyecto Final

© Copyright 2026. Todos los derechos reservados