Lo que necesitas saber de this en Javascript

this es una keyword en Javascript que describe…un concepto confuso. Uno cree comprenderlo pero cuando menos se lo espera de pronto el código no encuentra las referencias, no asigna los valores donde tu esperarías y no regresa lo que tu querías.

Me atrevería a decir que gran parte de este problema viene de otros lenguajes que también usan this (Java, PHP, C3, etc). No porque esten mal sino porque this en estos otros lenguajes no funciona igual que en JS y eso causa confusión para nosotros como desarrolladores.

Pero vienes precisamente a corregir eso. A entender de una vez por todas como funciona this. Y te voy a dar la respuesta de una vez.

La forma más fácil de entenderlo es visualizar a this como el objeto padre del execution context.

¿El padre de qué? ¿El execution context?

Para eso es el resto de este choro.

Execution Context

El execution context (contexto de ejecución) es el lugar donde Javascript ejecuta el código que esta corriendo.

Hay 2 tipos de execution contexts:

  • Global Execution Context
  • Function Execution Context

Puedes ver a los contextos de ejecución como los objetos que contienen referencias al código que se esta ejecutando. Estas referencias incluyen las variables y funciones que has creado y, en el caso del global execution context, también incluye aquellas propiedades que son intrínsecas al ambiente en donde se este ejecutando el código.

Y como ya te hice spoiler de seguro ya hilaste todos los conceptos. Si this es el execution context y el execution context es el objeto que tiene las referencias que estás llamando. Entonces this es ese objeto.

Muy cierto. Eres buenísimo en esto.

*En realidad el tema de los execution context es mas grande que solo eso. Sin embargo para el tema actual no es necesario ahondar mas en ello. Cuando termines de entender this ve directo a aprender más del Global Execution Context.

El objeto global

¿Qué objeto es el que representa el global execution context? ¿Cómo puedes ver ese objeto? Bueno…eso depende.

Tomemos de ejemplo los 2 ambientes más comúnes para ejecutar Javascript: el navegador y NodeJS.

Ambiente de EjecuciónObjeto GlobalPropiedades Intrínsecas
Navegadorwindowalert()<br>scrollTo()<br>focus()
Servidor – NodeJSglobalfetch()<br>require()

*Quiero pensar que si estas leyendo este post ya estas familiarizado con alguno de esos ambientes. Pero en caso contrario, todo lo que tienes que saber es que cada ambiente tiene funciones y propiedades que ayudan a funciones relacionadas con los ambientes: interactuar con los elementos de un sitio web para el navegador y hacer peticiones para NodeJS.

Tomando el navegador como ejemplo.

El global execution context usa el objeto global window y se lo asigna a la keyword this.

Y por lo tanto un this en el navegador…

console.log(this)

Regresa window:

*Este resultado lo puedes ver en la consola del navegador

Estas múltiples formas de acceder a un mismo objeto permiten llamar a las funciones de distintas maneras:

  • Llamar a la función directamente
  • Llamar a la función a través de window
  • Llamar a la función a través de this
alert('alert message')

window.alert('window alert message')

this.alert('this alert message')
Ventana de alert del navegador con el mensaje "alert message".
Ventana de alert del navegador con el mensaje "window alert message".
Ventana de alert del navegador con el mensaje "this alert message".

Como puedes ver las 3 líneas mandan a llamar las mismas funciones, pero con su mensaje respectivo.

Para las funciones y variables que tu creas ocurre lo mismo:

var theNumber = 689

function laugh() {
    console.log("XD")
}


console.log(theNumber) // 689
console.log(window.theNumber) // 689 
console.log(this.theNumber) // 689


laugh() // XD
window.laugh() // XD
this.laugh() // XD

Pero si esto es lo que pasa a nivel global (con el Global Execution Context) ¿qué pasa a nivel local?

this en una función

Si a nivel global se asigna el objeto del Global Execution Context a this entonces ¿pasa algo similiar dentro de una función?

No , exactamente. Bajo esa lógica uno esperaría que simplemente replicar la funcionalidad anterior dentro de una función sería mas que suficiente. Pero si lo haces podrás ver que el comportamiento difiere:

function container() {
    var theNumber = 689

    function laugh() {
        console.log("XD")
    }
    
    
    console.log(theNumber) // 689
    console.log(this.theNumber) // undefined
    
    laugh() // XD
    this.laugh() // Uncaught TypeError: this.laugh is not a function
}

container()

Justamente fallan las llamadas en las que se usa this. ¿Porqué ocurre esto?

Porque this esta utilizando el Global Execution Context y ese objeto no tiene ni la variable theNumber ni la función laugh. Ambas han sido declaradas dentro del scope de la función container.

Puedes comprobarlo si mandas a llamar this desde la función:

function container() {
    console.log(this) // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
    ...
}

container()

La explicación de porque ocurre esto es simple. Cuando tu quieras saber que execution context se asignó a this debes de hacerte la siguiente pregunta: ¿Qué objeto esta mandando a llamar a esta propiedad? Ese objeto es el que se le asigna a this.

Viendo el ejemplo anterior. ¿Qué objeto manda a llamar a container?

A pesar de que no se use explícitamente, el objeto global (window por estar corriendo el código en un navegador) es el que contiene la definción de container y le que se usa para llamarlo.


Ahora veamos un escenario en donde this toma otro valor.

this en un objeto

En el siguiente código se crea un objeto llamado car con 2 propiedades con valor nulo y 1 función. La función acepta 2 parámetros y asigna los valores correspondientes a las otras 2 propiedades.

Así mismo hay 2 variables globales con el mismo nombre que las propiedades de car.

¿Cuál de los 2 color se cambiará al ejecutarlo?

var color = 'green'

var car = {
    color: null,
    init (color) {
        this.color = color
    }
}

car.init('black')

Al agregar unos logs para debuggear queda mas claro lo que esta pasando durante la ejecución:

var color = 'green'

var car = {
    color: null,
    init (color) {
        this.color = color
        console.log(this.color) // black
    }
}

car.init('black')

console.log(car.color) // black
console.log(this.color) // green

El this dentro de init hace referencia al objeto de car. Por lo tanto this.color dentro de init es el mismo valor que el de car.color.

Por otro lado this en la última línea hace referencia al objeto global. Por lo tanto this.color es el valor de la propiedad global color de la primera línea.


*Si estas familiarizado con programación orientada a objetos, la estructura del ejemplo anterior te parecerá muy familiar a una clase. Puedes ver el mismo comportamiento si utilizas una clase en lugar del objeto.

var color = 'green'
var size = 'S'

class Car {
    color;
    size;
    constructor(color, size) {
        this.color = color
        this.size= size
        console.log(this.color) // black
    }
    
}

var carOne = new Car('black', 'XL')

console.log(carOne.color) // black
console.log(this.color) // green

Es importante que recuerdes que this toma el valor del objeto padre, es decir el que esta llamando a la propiedad que quieras usar. No siempre es fácil averiguar cual es el valor de this, pero con ayuda del inspector y este conocimiento podrás discernir más fácilmente en que lugares del código cambia el valor de this.