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ón | Objeto Global | Propiedades Intrínsecas |
Navegador | window | alert()<br>scrollTo()<br>focus() … |
Servidor – NodeJS | global | fetch()<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')
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
.