tutoriales.com

Desentrañando 'this' en JavaScript: Contexto de Ejecución y Enlace

El manejo de `this` en JavaScript es una de las áreas más confusas pero cruciales para los desarrolladores. Este tutorial desglosa sus diferentes comportamientos según el contexto de ejecución y te enseña a controlarlo, desde el enlace implícito hasta el léxico con arrow functions.

Intermedio18 min de lectura5 views23 de marzo de 2026Reportar error

El misterioso this en JavaScript es una fuente común de frustración y errores para muchos desarrolladores. A menudo, su valor parece cambiar "mágicamente" dependiendo de cómo se invoca una función. Sin embargo, una vez que comprendemos las reglas que rigen su enlace, this se convierte en una herramienta poderosa y predecible.

En este tutorial, desentrañaremos el valor de this en diferentes escenarios, exploraremos las reglas de enlace que JavaScript aplica y aprenderemos a manipularlo para escribir código más robusto y comprensible.

📌 ¿Qué es this en JavaScript? La Gran Pregunta

En esencia, this es una palabra clave que hace referencia al contexto de ejecución de una función. Es decir, a qué objeto "pertenece" la función cuando se invoca. No es una variable estática; su valor se determina en el momento de la invocación de la función, no en el momento de su definición.

🔥 Importante: El valor de `this` no se basa en dónde se declara una función, sino en cómo se llama.

Imagina que estás en una fiesta y alguien te pregunta "¿Quién eres ?". Tu respuesta dependerá de con quién estés hablando y en qué contexto. Si te pregunta un amigo, te identificarás como tú mismo. Si te pregunta un guardia de seguridad en la puerta, te identificarás como "el invitado con invitación X". this funciona de manera similar: su identidad cambia según la situación.

📖 Reglas de Enlace de this: Las Cuatro Fundamentales

JavaScript tiene un conjunto de reglas bien definidas para determinar el valor de this. Entenderlas es la clave para dominar this.

Las principales reglas de enlace son:

  1. Enlace por Defecto (Global)
  2. Enlace Implícito (Contexto de Objeto)
  3. Enlace Explícito (call, apply, bind)
  4. Enlace new (Constructores)
  5. Enlace Léxico (Arrow Functions)

Vamos a desglosar cada una de ellas.


1. Enlace por Defecto (Global) 🌍

Cuando una función se invoca de forma "sencilla" o "desconectada", sin un objeto precediéndola (es decir, no es un método de un objeto) y no se aplica ninguna otra regla de enlace, this por defecto apunta al objeto global.

En el navegador, el objeto global es window. En Node.js, es global (o undefined en modo estricto para funciones regulares).

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

mostrarThis(); // En el navegador: window; En Node.js (modo no estricto): global

function otroEjemplo() {
  'use strict'; // Habilitar modo estricto
  console.log(this);
}

otroEjemplo(); // En modo estricto: undefined

var a = 10;
console.log(window.a); // 10 (en navegador, 'a' se adjunta a window)
⚠️ Advertencia: El modo estricto (`'use strict';`) cambia el comportamiento del enlace por defecto, haciendo que `this` sea `undefined` en lugar del objeto global. Es una buena práctica usar el modo estricto para evitar efectos secundarios inesperados.

2. Enlace Implícito (Contexto de Objeto) 📦

Esta es la regla más común y la que probablemente has encontrado más a menudo. Cuando una función se invoca como un método de un objeto, this se enlaza automáticamente a ese objeto.

La clave aquí es el objeto que precede el punto (.) en la llamada a la función.

const persona = {
  nombre: 'Alicia',
  saludar: function() {
    console.log(`Hola, soy ${this.nombre}`);
  },
  direccion: {
    calle: 'Calle Falsa 123',
    mostrarCalle: function() {
      console.log(`Mi calle es ${this.calle}`);
    }
  }
};

persona.saludar(); // Hola, soy Alicia (this es 'persona')
persona.direccion.mostrarCalle(); // Mi calle es Calle Falsa 123 (this es 'persona.direccion')

Problemas con el Enlace Implícito 🤕

El enlace implícito puede romperse fácilmente cuando se "extrae" una función de su objeto:

const persona = {
  nombre: 'Bob',
  saludar: function() {
    console.log(`Hola, soy ${this.nombre}`);
  }
};

const miFuncionSaludar = persona.saludar;

miFuncionSaludar(); // Hola, soy undefined (this es window/global o undefined en modo estricto)

En este caso, miFuncionSaludar se invoca sin un objeto precedente, por lo que cae en la regla de enlace por defecto. ¡this ya no apunta a persona!

Otro ejemplo común es con setTimeout:

const contador = {
  valor: 0,
  incrementar: function() {
    this.valor++;
    console.log(this.valor);
  },
  iniciarConRetraso: function() {
    setTimeout(this.incrementar, 1000); // ¡Problema aquí!
  }
};

contador.iniciarConRetraso(); // Después de 1 segundo: NaN (o error si 'valor' no es un número)
                               // porque 'this' dentro de incrementar es window/global

Aquí, setTimeout recibe la función incrementar sin su contexto. setTimeout ejecuta incrementar con el enlace por defecto, donde this es window (y window.valor es undefined, undefined++ es NaN).


3. Enlace Explícito (call, apply, bind) 🛠️

Para resolver los problemas del enlace implícito o simplemente para forzar this a un objeto específico, JavaScript nos proporciona métodos especiales en Function.prototype:

  • call(thisArg, arg1, arg2, ...)
  • apply(thisArg, [arg1, arg2, ...])
  • bind(thisArg, arg1, arg2, ...)

call() y apply()

Ambos call y apply permiten invocar una función inmediatamente, estableciendo this a un valor específico que pasamos como primer argumento. La única diferencia es cómo pasan los argumentos de la función:

  • call() acepta argumentos individualmente (separados por comas).
  • apply() acepta argumentos como un array.
function describirPersona(ocupacion, ciudad) {
  console.log(`Mi nombre es ${this.nombre}, soy ${ocupacion} y vivo en ${ciudad}.`);
}

const persona1 = { nombre: 'Carlos' };
const persona2 = { nombre: 'María' };

describirPersona.call(persona1, 'ingeniero', 'Madrid');
// Mi nombre es Carlos, soy ingeniero y vivo en Madrid.

describirPersona.apply(persona2, ['diseñadora', 'Barcelona']);
// Mi nombre es María, soy diseñadora y vivo en Barcelona.

Estos métodos son útiles para "pedir prestadas" funciones o para asegurar un contexto específico en una invocación única.

💡 Consejo: Usa `call` cuando tienes un número fijo de argumentos conocidos, y `apply` cuando los argumentos están en un array (por ejemplo, cuando los recibes de otra función o de `arguments`).

bind()

A diferencia de call y apply, bind() no invoca la función inmediatamente. En su lugar, devuelve una nueva función con this permanentemente enlazado al valor que le pasaste. Esta nueva función "recordará" su this enlazado, sin importar cómo se le llame después.

function saludar() {
  console.log(`Hola, ${this.nombre}`);
}

const usuario = { nombre: 'Laura' };

const saludarUsuario = saludar.bind(usuario);

saludarUsuario(); // Hola, Laura

// Podemos pasarla a setTimeout sin perder el contexto
setTimeout(saludarUsuario, 1000); // Hola, Laura (después de 1 segundo)

// O a un manejador de eventos
// document.getElementById('btn').addEventListener('click', saludarUsuario);

bind es la solución perfecta para el problema de setTimeout que vimos antes:

const contadorMejorado = {
  valor: 0,
  incrementar: function() {
    this.valor++;
    console.log(this.valor);
  },
  iniciarConRetraso: function() {
    // Enlazamos 'this' de incrementar al objeto 'contadorMejorado'
    setTimeout(this.incrementar.bind(this), 1000); 
  }
};

contadorMejorado.iniciarConRetraso(); // Después de 1 segundo: 1

4. Enlace new (Constructores) 🏗️

Cuando una función se invoca con la palabra clave new, se comporta como una función constructora. El operador new realiza cuatro pasos:

  1. Crea un nuevo objeto vacío.
  2. Establece el this de la función constructora para que apunte a este nuevo objeto.
  3. Ejecuta el código de la función constructora, la cual inicializa el nuevo objeto.
  4. Si la función constructora no devuelve explícitamente un objeto, new devuelve el nuevo objeto creado en el paso 1.
function Coche(marca, modelo) {
  this.marca = marca;
  this.modelo = modelo;
  this.mostrarInfo = function() {
    console.log(`Coche: ${this.marca} ${this.modelo}`);
  };
}

const miCoche = new Coche('Toyota', 'Corolla');
const otroCoche = new Coche('Honda', 'Civic');

miCoche.mostrarInfo();  // Coche: Toyota Corolla
otroCoche.mostrarInfo(); // Coche: Honda Civic

En este caso, this dentro de Coche apunta al nuevo objeto (miCoche o otroCoche) que se está creando.


5. Enlace Léxico (Arrow Functions) ➡️

Las arrow functions (=>) introducidas en ES6 tienen un comportamiento de this fundamentalmente diferente al de las funciones regulares. Las arrow functions no tienen su propio this. En su lugar, heredan el valor de this de su contexto léxico (el ámbito circundante donde fueron definidas).

Esto las hace extremadamente útiles para problemas como el de setTimeout o cuando se usan callbacks dentro de métodos de objetos.

const desarrollador = {
  nombre: 'Daniel',
  habilidades: ['JS', 'React', 'Node'],
  mostrarHabilidades: function() {
    this.habilidades.forEach(function(habilidad) {
      // 'this' aquí es window/global (o undefined en modo estricto)!
      console.log(`${this.nombre} sabe ${habilidad}`); // Error: this.nombre es undefined
    });
  },
  mostrarHabilidadesConArrow: function() {
    this.habilidades.forEach(habilidad => {
      // 'this' aquí es el 'this' de 'mostrarHabilidadesConArrow', que es 'desarrollador'
      console.log(`${this.nombre} sabe ${habilidad}`); 
    });
  }
};

desarrollador.mostrarHabilidades(); // Intentará usar window.nombre
desarrollador.mostrarHabilidadesConArrow(); 
// Daniel sabe JS
// Daniel sabe React
// Daniel sabe Node

En el primer ejemplo, la función anónima pasada a forEach es una función regular, por lo que su this se enlaza por defecto. En el segundo, la arrow function hereda el this del método mostrarHabilidadesConArrow, que en ese momento es desarrollador.

Esto simplifica enormemente el manejo de this en muchos escenarios con callbacks.

📌 Nota: Debido a su naturaleza léxica, las arrow functions ignoran `call`, `apply` y `bind` en lo que respecta a su `this`. El `this` que reciben es el de su entorno de definición y no se puede sobrescribir con estos métodos.

🤯 Precedencia de las Reglas de Enlace

Cuando se aplican múltiples reglas, ¿cuál gana? JavaScript sigue un orden de precedencia:

  1. new binding (el new es el más fuerte)
  2. Explicit binding (call, apply, bind)
  3. Implicit binding (contexto de objeto)
  4. Default binding (global/undefined)

Las arrow functions tienen su propia regla, que es la léxica, y no encajan en esta jerarquía tradicional porque simplemente no tienen su propio this mutable.

Ejemplo de Precedencia: `bind` vs. `new`
function Foco(nombre) {
  this.nombre = nombre;
  console.log(`Creando foco: ${this.nombre}`);
}

const FocoRojo = Foco.bind(null, 'Rojo'); // Enlaza 'this' a null, y 'nombre' a 'Rojo'

const focoUno = new FocoRojo(); // Cuando se usa 'new', 'bind' es ignorado para 'this'
                               // pero los argumentos de 'bind' (como 'Rojo') se mantienen.
// Salida: Creando foco: Rojo
console.log(focoUno.nombre); // Rojo (el 'new' tiene precedencia sobre el 'null' de bind, pero el argumento 'Rojo' se aplica)

En este caso, new tiene mayor precedencia para el this, pero bind sigue aplicando los argumentos que preestableció.


🛠️ Patrones y Buenas Prácticas para this

  1. that = this (self/context): Un patrón antiguo pero aún útil antes de las arrow functions para preservar el contexto de this en callbacks anidados.
const jugador = {
puntos: 100,
aumentarPuntos: function() {
const that = this; // Guarda el 'this' externo
setTimeout(function() {
that.puntos += 10; // Usa el 'this' guardado
console.log(`Puntos: ${that.puntos}`);
}, 500);
}
};
jugador.aumentarPuntos(); // Puntos: 110 (después de 0.5s)
  1. Usar bind para Callbacks: Ideal para asegurar que this se mantenga constante en eventos o funciones asíncronas.
class Boton {
constructor(texto) {
this.texto = texto;
this.elemento = document.createElement('button');
this.elemento.textContent = this.texto;
this.elemento.addEventListener('click', this.onClick.bind(this));
document.body.appendChild(this.elemento);
}

onClick() {
console.log(`Botón ${this.texto} clicado.`); // 'this' es la instancia de Boton
}
}

new Boton('Enviar');
  1. Arrow Functions para Callbacks Internos: La forma más moderna y limpia de resolver el problema this en funciones anidadas.
const gestionadorTareas = {
tareas: ['Comprar leche', 'Estudiar JS'],
listarTareas: function() {
this.tareas.forEach(tarea => {
console.log(`- ${tarea} (Asignada a: ${this.tareas.length > 0 ? 'Mí mismo' : 'Nadie'})`); // 'this' es gestionadorTareas
});
}
};
gestionadorTareas.listarTareas();
  1. Clases y this: En los métodos de clase, this se refiere a la instancia de la clase. Es el enlace implícito en acción. Sin embargo, en los constructores de clase, si no usas super(), this no está disponible antes de la llamada a super() en subclases.

    ⚠️ Advertencia: Si estás usando métodos de clase como callbacks (por ejemplo, en `addEventListener`), ¡recuerda que podrías necesitar `.bind(this)` o usar arrow functions en la definición del método para preservar el contexto!
class Usuario {
constructor(nombre) {
this.nombre = nombre;
}

saludar() {
console.log(`Hola, soy ${this.nombre}`);
}

// Opción 1: Usar un método arrow para enlazar 'this' automáticamente
// saludarArrow = () => {
//   console.log(`Hola, soy ${this.nombre} (desde arrow)`);
// }
}

const user = new Usuario('Ana');
user.saludar(); // Hola, soy Ana

// Si pasamos user.saludar como callback, el 'this' se pierde
// setTimeout(user.saludar, 1000); // Hola, soy undefined (o error en strict mode)

// Solución con bind:
setTimeout(user.saludar.bind(user), 1000); // Hola, soy Ana

// Solución con arrow function en la definición del método (si estuviera definida como arriba)
// setTimeout(user.saludarArrow, 1000); // Hola, soy Ana (desde arrow)
💡 Consejo: Para métodos de clase que serán usados como callbacks, definir el método como una arrow function dentro de la clase (e.g., `miMetodo = () => { ... }`) es una forma concisa y moderna de asegurar que `this` siempre esté enlazado a la instancia de la clase. Esto se conoce como *public class fields syntax*.

📊 Tabla Comparativa de Enlaces de this

Tipo de EnlaceCómo se invocaValor de thisEjemplos
Por Defectofn(); (llamada simple)window (navegador) / global (Node.js) si no es strict mode. undefined en strict mode.console.log(this); (global)
Implícitoobj.method(); (como método de un objeto)El obj que precede el punto.persona.saludar();
Explícito (call/apply)fn.call(obj, ...); / fn.apply(obj, [...]);`El obj pasado como primer argumento.saludar.call(persona, ...);
Explícito (bind)const newFn = fn.bind(obj); newFn();El obj pasado como primer argumento, enlazado permanentemente a la nueva función.setTimeout(fn.bind(obj), 100);
newnew Constructor();Un objeto recién creado.new Coche('Ford');
Léxico (Arrow Fn)() => { ... }; (dentro de otro scope)Hereda this del ámbito léxico circundante.array.forEach(item => console.log(this));

🤯 Ejemplos Avanzados y Casos Especiales

this en Event Handlers

Cuando una función se usa como un event handler (por ejemplo, con addEventListener), el this dentro de esa función generalmente se refiere al elemento DOM al que está adjunto el listener.

const boton = document.createElement('button');
boton.textContent = 'Haz clic';
document.body.appendChild(boton);

boton.addEventListener('click', function() {
  console.log(this.textContent); // 'Haz clic' (this es el botón)
  this.style.backgroundColor = 'lightblue'; // Modifica el botón
});

Si necesitas acceder al this de un objeto externo dentro del event handler, ahí es donde bind o las arrow functions son cruciales:

class ContadorClicks {
  constructor(id) {
    this.clicks = 0;
    this.elemento = document.getElementById(id);
    this.elemento.addEventListener('click', this.handleClick);
  }

  handleClick() {
    // ¡Problema! 'this' aquí es el elemento DOM, no la instancia de ContadorClicks
    this.clicks++; // undefined++
    console.log(`Clicks: ${this.clicks}`); 
  }

  // Solución 1: Enlazar en el constructor
  // constructor(id) {
  //   ...
  //   this.elemento.addEventListener('click', this.handleClick.bind(this));
  // }

  // Solución 2: Usar arrow function como método de clase
  // handleClick = () => {
  //   this.clicks++; // 'this' es la instancia de ContadorClicks
  //   console.log(`Clicks: ${this.clicks}`);
  // }
}

// Suponiendo un <button id="miBoton">...</button>
// new ContadorClicks('miBoton');
Llamada a la función ¿Usó 'new'? Nuevo objeto No ¿call / apply / bind? Objeto explícito No ¿Es método de objeto? Objeto precedente No ¿Es arrow function? Contexto léxico padre No Objeto global / undefined

this en Clases y Herencia

Cuando trabajas con clases y herencia en JavaScript, el manejo de this sigue las mismas reglas fundamentales, pero con algunas particularidades importantes:

  • Dentro de un constructor de clase: this se refiere a la nueva instancia de la clase que se está creando. En una subclase, super() debe ser llamado antes de usar this, ya que super() es quien inicializa this en el contexto de la clase padre.
class Animal {
constructor(nombre) {
this.nombre = nombre;
}
}

class Perro extends Animal {
constructor(nombre, raza) {
super(nombre); // Llama al constructor de la clase padre (Animal)
this.raza = raza;
// Ahora 'this' está disponible para usar
console.log(`Creando perro ${this.nombre} de raza ${this.raza}`);
}
}

new Perro('Fido', 'Labrador'); // Creando perro Fido de raza Labrador
  • Dentro de métodos de instancia: this se refiere a la instancia específica de la clase sobre la que se invoca el método (enlace implícito).
class Gato {
constructor(nombre) {
this.nombre = nombre;
}

maullar() {
console.log(`${this.nombre} dice: ¡Miau!`);
}
}

const miGato = new Gato('Misifú');
miGato.maullar(); // Misifú dice: ¡Miau!
🔥 Importante: Si un método de una clase se pasa como callback a una función externa (ej. `setTimeout`, `addEventListener`), el contexto `this` se perderá y necesitarás `bind(this)` o usar arrow functions para preservar el enlace a la instancia.

✅ Conclusión: Domina this y Libera tu JavaScript

Comprender cómo se enlaza this es fundamental para escribir código JavaScript efectivo y libre de errores. Aunque al principio pueda parecer confuso, las reglas son consistentes y predecibles.

Recuerda la jerarquía de las reglas de enlace y cuándo cada una entra en juego. Las arrow functions han simplificado muchos de los dolores de cabeza de this en callbacks, pero los métodos call, apply y bind siguen siendo herramientas indispensables para un control explícito.

Con práctica y atención a cómo se invoca una función, this dejará de ser un misterio y se convertirá en un aliado valioso en tu arsenal de JavaScript.

¡Sigue practicando y construyendo! El dominio de this es una señal de que estás avanzando hacia un nivel más profundo de comprensión de JavaScript.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!