Ir al contenido principal

This en objetos

Introducción

La palabra clave this es uno de los conceptos más importantes y, a la vez, más confusos de JavaScript. Funciona de manera diferente a otros lenguajes de programación y su valor cambia según el contexto de ejecución. En este artículo exploraremos a fondo el comportamiento de this en el contexto de los objetos, cómo se determina su valor, los problemas comunes que pueden surgir y las técnicas para controlar su comportamiento. Dominar el uso de this es fundamental para escribir código orientado a objetos efectivo en JavaScript.

Concepto de this en JavaScript

En JavaScript, this es una referencia que se establece automáticamente cuando se ejecuta una función, apuntando al "dueño" de esa función. A diferencia de otros lenguajes donde this siempre apunta a la instancia del objeto, en JavaScript el valor de this es dinámico y se determina en el momento de la invocación, no en el momento de la definición.

this es como un "pronombre" que permite a los métodos acceder a las propiedades y otros métodos del mismo objeto sin tener que repetir el nombre del objeto.

const usuario = {
  nombre: "Sara",
  edad: 32,
  saludar() {
    console.log(`Hola, soy ${this.nombre} y tengo ${this.edad} años`);
  }
};

usuario.saludar(); // Hola, soy Sara y tengo 32 años

En este ejemplo, this dentro del método saludar() hace referencia al objeto usuario, permitiendo acceder a sus propiedades.

Comportamiento de this en métodos de objetos

Cuando una función se ejecuta como método de un objeto, this referencia al objeto que contiene el método.

const producto = {
  nombre: "Laptop",
  precio: 1200,
  mostrarInfo() {
    console.log(`${this.nombre} - Precio: ${this.precio}€`);
  },
  aplicarDescuento(porcentaje) {
    this.precio = this.precio - (this.precio * porcentaje / 100);
    console.log(`Nuevo precio con ${porcentaje}% de descuento: ${this.precio}€`);
  }
};

producto.mostrarInfo(); // Laptop - Precio: 1200€
producto.aplicarDescuento(10); // Nuevo precio con 10% de descuento: 1080€
producto.mostrarInfo(); // Laptop - Precio: 1080€

En ambos métodos, this hace referencia al objeto producto, lo que permite acceder y modificar sus propiedades.

This en diferentes contextos

El valor de this varía dependiendo del contexto en que se ejecuta la función:

1. Contexto global

En el contexto global (fuera de cualquier función), this hace referencia al objeto global, que en los navegadores es window y en Node.js es global.

console.log(this === window); // true (en navegadores)

let edad = 30;
console.log(this.edad); // 30 (en navegadores)

2. Funciones regulares

En funciones regulares (no métodos), el valor de this depende del modo estricto:

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

mostrarThis(); // window (en navegadores, modo no estricto)

Con modo estricto ("use strict"), this será undefined:

"use strict";
function mostrarThis() {
  console.log(this);
}

mostrarThis(); // undefined

3. Métodos de objetos

Como ya vimos, en métodos de objetos, this hace referencia al objeto que contiene el método.

4. Constructores

En funciones constructoras (llamadas con new), this hace referencia al nuevo objeto que está siendo creado:

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

const persona1 = new Persona("Miguel", 25);
persona1.saludar(); // Hola, soy Miguel

5. Eventos DOM

En manejadores de eventos, this normalmente hace referencia al elemento que disparó el evento:

const boton = document.querySelector("button");
boton.addEventListener("click", function() {
  console.log(this); // Muestra el elemento button
  this.style.backgroundColor = "red";
});

Problemas comunes con this

El comportamiento dinámico de this puede llevar a situaciones confusas. Aquí veremos los problemas más frecuentes:

Pérdida de contexto

El problema más común ocurre cuando extraemos un método de un objeto y lo ejecutamos como una función independiente:

const coche = {
  marca: "Toyota",
  mostrarMarca() {
    console.log(this.marca);
  }
};

coche.mostrarMarca(); // Toyota

// Guardamos el método en una variable
const funcionSuelta = coche.mostrarMarca;
funcionSuelta(); // undefined - ¡perdimos el contexto!

Al asignar el método a una variable y ejecutarlo directamente, this ya no apunta al objeto coche, sino que se determina por las reglas de llamada de función regular (que apunta a window o undefined en modo estricto).

Contexto en funciones anidadas

Otro problema ocurre con funciones anidadas dentro de métodos:

const persona = {
  nombre: "Ana",
  hobbies: ["leer", "correr", "cocinar"],
  mostrarHobbies() {
    this.hobbies.forEach(function(hobby) {
      // "this" aquí no es el objeto persona
      console.log(`${this.nombre} disfruta ${hobby}`);
    });
  }
};

persona.mostrarHobbies();
// undefined disfruta leer
// undefined disfruta correr
// undefined disfruta cocinar

Dentro de la función callback del forEach, this no se refiere al objeto persona, sino que sigue las reglas de una función regular.

Métodos call(), apply() y bind()

JavaScript proporciona tres métodos que nos permiten controlar explícitamente el valor de this en una función:

call()

El método call() invoca una función con un valor this específico y argumentos proporcionados individualmente:

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

const persona = { nombre: "Luis" };

saludar.call(persona, "Hola"); // Hola, soy Luis

apply()

El método apply() es similar a call(), pero recibe los argumentos como un array:

function presentarse(saludo, despedida) {
  console.log(`${saludo}, soy ${this.nombre}. ${despedida}`);
}

const persona = { nombre: "Carmen" };

presentarse.apply(persona, ["Buenas tardes", "¡Hasta pronto!"]); 
// Buenas tardes, soy Carmen. ¡Hasta pronto!

bind()

A diferencia de call() y apply() que ejecutan la función inmediatamente, bind() crea una nueva función con el valor this permanentemente vinculado:

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

const saludarPablo = persona.saludar.bind(persona);
saludarPablo(); // Hola, soy Pablo

// Incluso si asignamos la función a otra variable u objeto
const otraVariable = saludarPablo;
otraVariable(); // Hola, soy Pablo

Soluciones a problemas comunes

Usando estas técnicas, podemos solucionar los problemas mencionados anteriormente:

// Solución al problema de contexto en forEach
const persona = {
  nombre: "Ana",
  hobbies: ["leer", "correr", "cocinar"],
  mostrarHobbies() {
    // Solución 1: usar bind
    this.hobbies.forEach(function(hobby) {
      console.log(`${this.nombre} disfruta ${hobby}`);
    }.bind(this));
    
    // Solución 2: guardar referencia a this
    const self = this;
    this.hobbies.forEach(function(hobby) {
      console.log(`${self.nombre} disfruta ${hobby}`);
    });
    
    // Solución 3: usar arrow function
    this.hobbies.forEach(hobby => {
      console.log(`${this.nombre} disfruta ${hobby}`);
    });
  }
};

persona.mostrarHobbies();
// Ana disfruta leer
// Ana disfruta correr
// Ana disfruta cocinar

This en funciones flecha vs. funciones regulares

Las funciones flecha (introducidas en ES6) tienen un comportamiento especial con respecto a this. A diferencia de las funciones regulares, no tienen su propio valor de this. En su lugar, heredan el valor de this del contexto circundante (léxico):

const equipo = {
  nombre: "Estrellas FC",
  jugadores: ["Carlos", "David", "Elena"],
  mostrarJugadores() {
    // La función flecha hereda "this" del método mostrarJugadores
    this.jugadores.forEach(jugador => {
      console.log(`${jugador} juega en ${this.nombre}`);
    });
  }
};

equipo.mostrarJugadores();
// Carlos juega en Estrellas FC
// David juega en Estrellas FC
// Elena juega en Estrellas FC

Cuándo no usar funciones flecha

Debido a su comportamiento especial con this, las funciones flecha no son adecuadas en todos los contextos:

  1. Como métodos de objetos: Una función flecha no tiene su propio this, por lo que no puede acceder correctamente a las propiedades del objeto:
const persona = {
  nombre: "Julia",
  // ¡Problema! La función flecha no tiene su propio this
  saludar: () => {
    console.log(`Hola, soy ${this.nombre}`);
  }
};

persona.saludar(); // Hola, soy undefined
  1. Con constructores: Las funciones flecha no pueden usarse como constructores ya que no crean un nuevo contexto de this:
const Persona = (nombre) => {
  this.nombre = nombre; // "this" no hace referencia al nuevo objeto
};

// Esto dará error
const persona = new Persona("Raúl"); // TypeError: Persona is not a constructor
  1. En manejadores de eventos DOM donde necesitas que this sea el elemento:
const boton = document.querySelector("button");

// Problema: "this" no será el botón
boton.addEventListener("click", () => {
  console.log(this); // window o el contexto superior, no el botón
});

// Correcto: "this" será el botón
boton.addEventListener("click", function() {
  console.log(this); // el elemento button
});

Patrones para manejar el contexto

Existen varios patrones comunes para gestionar el contexto de this:

1. Método bind en constructores

function Contador() {
  this.count = 0;
  this.incrementar = this.incrementar.bind(this);
}

Contador.prototype.incrementar = function() {
  this.count++;
  console.log(this.count);
};

const contador = new Contador();
const incrementar = contador.incrementar;
incrementar(); // 1 (funciona gracias a bind)

2. Guardar referencia a this

const reproductor = {
  canciones: ["Canción 1", "Canción 2", "Canción 3"],
  reproducir() {
    const self = this;
    setTimeout(function() {
      console.log(`Reproduciendo: ${self.canciones[0]}`);
    }, 1000);
  }
};

reproductor.reproducir(); // Reproduciendo: Canción 1

3. Usar arrow functions

const reproductor = {
  canciones: ["Canción 1", "Canción 2", "Canción 3"],
  reproducir() {
    setTimeout(() => {
      console.log(`Reproduciendo: ${this.canciones[0]}`);
    }, 1000);
  }
};

reproductor.reproducir(); // Reproduciendo: Canción 1

Buenas prácticas para evitar confusiones

  1. Evita funciones anidadas cuando sea posible, o usa arrow functions para mantener el contexto.

  2. No mezcles estilos de funciones sin necesidad. Si estás trabajando con this, mantén un enfoque consistente.

  3. Utiliza this solo cuando sea necesario. A veces, evitar this puede llevar a un código más claro y menos propenso a errores.

  4. Usa ESLint o similares para detectar problemas potenciales con el uso de this.

  5. Documenta claramente el comportamiento esperado de this en tus funciones, especialmente en código que será utilizado por otros.

  6. Considera enfoques funcionales para evitar el uso excesivo de this:

// Enfoque con this
const calculadora = {
  valor: 0,
  sumar(num) {
    this.valor += num;
    return this;
  },
  restar(num) {
    this.valor -= num;
    return this;
  },
  obtenerValor() {
    return this.valor;
  }
};

// Enfoque funcional (sin this)
function crearCalculadora(valorInicial = 0) {
  let valor = valorInicial;
  
  return {
    sumar: (num) => {
      valor += num;
      return this;
    },
    restar: (num) => {
      valor -= num;
      return this;
    },
    obtenerValor: () => valor
  };
}

Resumen

El comportamiento de this en JavaScript es dinámico y se determina en el momento de la invocación de la función, no en el momento de su definición. En métodos de objetos, this hace referencia al objeto que contiene el método, pero este comportamiento puede cambiar en diferentes contextos como funciones anidadas o callbacks.

Para controlar el valor de this, disponemos de métodos como call(), apply() y bind(), además de las funciones flecha que heredan this del contexto circundante. Conocer y entender estas particularidades es esencial para escribir código JavaScript orientado a objetos que sea robusto y evitar errores comunes relacionados con el contexto.

Recuerda que no hay una única forma "correcta" de manejar this en todos los escenarios. La elección dependerá del caso específico, el estilo de programación y los requisitos del proyecto.