Let’s talk about THIS

Hace mucho que no escribo y lo voy a hacer sobre un amigo del Javascript, algunos le odian, otros le aman… Lo cierto es que, usado con cabeza, puede hacer grandes cosas, pero siempre hay que tener claro que el mismo poder tiene para hacer de nuestro código el rey de la eficiencia como de convertirlo en un infierno. Estoy hablando, por supuesto, de this.

This resulta complejo porque puede cambiar su valor dependiendo de quién lo ejecute y, si no lo controlamos bien, puede darnos resultados inesperados.

Antes de verlo con ejemplos, vamos a partir de la idea de que, por lo general, el valor de this viene determinado por cómo se invoca la función y por el propietario de ésta:

En este caso, estamos definiendo una función, pero no forma parte de una estructura definida, como por ejemplo, un objeto con métodos y propiedades, por tanto, cuando la invoquemos se hará en el contexto global.

Por lo tanto, this es window, que es el propietario del contexto global

Otro caso sería que el propietario sea un objeto, como en este ejemplo:

Al ejecutar el método por tanto me devuelve el valor del objeto propietario del método que se ha invocado:

LA TRAMPA

Pero no todo es blanco o negro. Hay que vigilar mucho cómo están definidas las funciones que usan this, ya que podemos meternos sin quererlo en trampas…

¿Por qué es tan peliagudo el this?

Concepto de propietario

El concepto de propietario puede inducir a errores, porque asumimos que si un objeto tiene definidas unas propiedades y métodos, éstos ya están delimitados y harán referencia a mi objeto. Pero fijaos en el siguiente ejemplo:

Como vemos, tenemos un objeto con su propiedades y si llamamos a alguna de ellas, nos devuelve lo que esperamos.

Ahora vamos a introducir el this en otra propiedad que nos de el nombre y el apellido juntos:

Si ejecutamos …

console.log(miObject.fullName)

…lo que vamos a obtener es undefined. Ohhh, ¿y eso por qué? Porque esa propiedad se está definiendo al mismo tiempo que el nombre y apellido, así que no hace referencia al objeto (porque aún no existe) y por tanto sus propiedades, sino que su propietario más próximo es el contexto global (window).

¿Y cómo se podría solucionar? ¿Cómo podemos decirle que el this va a ser cada una de las otras propiedades del objeto? Aquí está la clave: cambiando al propietario de la invocación.

PATRONES DE INVOCACIÓN

Aquí es donde entran en el juego los patrones de invocación, que nos van a permitir inicializar el this de distintas maneras.

Invocación como método

En nuestro ejemplo anterior lo que haríamos sería crear un método:

¿Por qué? Porque cuando creas un método éste tiene un propietario, que es el objeto que lo ha creado, así pues, cuando se ejecute, sabrá que su propietario es dicho objeto y, por tanto, this hará referencia a éste y a sus propiedades.

Console.log(miObject.fullName()) // Aida Albarrán

Invocación como función. Método bind y Arrow Functions

Nos referimos a la invocación de una función que no forma parte de un objeto. Cuando invocamos una función de este tipo, el contexto de this es el contexto global, puesto que la función no forma parte del objeto, como hemos dicho.

Modificando el ejemplo anterior,

Tal y como hemos mencionado, si ejecutamos el método fullName, no obtendremos el resultado esperado correspondiente a la función giveFullName:

miObjectFunctionPattern.fullName("Albarrán");
console.log(miObjectFunctionPattern.name) // Aida

Eso lo podemos solucionar redirigiendo el valor de this dentro del contexto en el que queremos usarlo, por ejemplo con self o con that.

De este modo, cuando invoquemos la función giveFullName desde el método fullName, dicha función cogerá el contexto especificado con self, que es el esperado.

miObjectFunctionPattern.fullName("Albarrán");
console.log(miObjectFunctionPattern.name) // Aida Albarrán

Otra forma de solucionarlo sería con el método bind. Este método lo que nos permite es, a partir de una función dada, realizar una copia, pero asignándole a this el contexto que queremos. En nuestro ejemplo, lo haríamos así:

Con esto obtendríamos el mismo resultado que usando self.

Aún hay otra solución: las Arrow Functions o Funciones Flecha. Las Arrow Functions permiten que el valor de this sea el del contexto más cercano, sin necesidad de “bindearlo”. Lo usaríamos así:

Invocación como constructor

En este punto, conviene recordar que Javascript es un lenguaje de herencia prototipada. Todo son objetos y cada objeto es creado usando otro objeto como prototipo. Esto implica que, por tanto, tengan las características propias de los objetos, como son métodos y propiedades.

Por ejemplo, si definimos una función, asumimos que ésta es en sí un objeto, una instancia de Function, que tiene sus propios métodos y propiedades.

Por ejemplo:

Esta función declarada tiene ya intrínsecos unos métodos y propiedades que tiene definidos como objeto que es, tales como:

console.log(myObjectFunction.name) // myObjectFunction
console.log(myObjectFunction.toString()) // function (parameter1, parameter2) {
console.log(`Mi función imprime ${parameter1} y ${parameter2}`);
}

Además de éstos, tiene otros métodos que nos pueden resultar útiles para manejar this, como son call y apply, que veremos a continuación o bind, que hemos visto más arriba.

De momento, vamos a centrarnos en la solución que ofrece la invocación con constructor.

Javascript ofrece un modo de crear objetos que pretende acercarse al estilo de los lenguajes de programación clásicos orientados a objetos y que facilitan de este modo su uso. Es el caso del new.

Vamos a verlo adaptando nuestro ejemplo de antes

Primero definimos un constructor:

Y un método prototipo:

Y finalmente instanciamos otro objeto con new:

var otherObject = new MyObjectFunction();

Si ejecutamos el método prototipo del constructor ¿qué obtendremos?

otherObject.showFullName(); // Imprime nombre completo: Aida Albarrán

Como hemos visto, se ha ejecutado el this en el contexto del propietario, el constructor.

También podríamos hacerlo usando la expresión class, que es el modo que ha creado Javascript para facilitar la definición de constructores y manejo de la herencia.

var objectWithClass = new myClassObjectFunction()
objectWithClass.showFullName(); // Imprime nombre completo: Aida Albarrán

Ahora bien… ¿Y si quisiésemos cambiar el contexto del prototipo por el que yo quiera?

Entonces entra en juego nuestro siguiente patrón de invocación:

Invocación con call y apply

Como hemos mencionado antes, hay varios métodos que están dentro de cada objeto de forma intrínseca, como son estos dos.

Lo que hacen estos métodos es permitirnos aplicar otro contexto a una función. Además, nos permiten introducir argumentos, de este modo, cuando aplicamos apply o call, el primer parámetro que indicamos siempre será el contexto de this y el segundo o siguientes, los parámetros propios de la función.

La diferencia entre call y apply es que con call el segundo parámetro y siguientes van separados por comas, mientras que el apply recibe como segundo parámetro un array.

Adaptando nuestro ejemplo anterior, nos crearíamos por un lado un nuevo objeto con sus propiedades:

Y a la hora de ejecutar nuestro método que nos devuelve el nombre completo, indicaríamos que el contexto de this, en lugar de ser el constructor, va a ser myOtherObject, de este modo:

otherObject.showFullName.call(myOtherObject)

Nos da como resultado en la consola:

Imprime nombre completo: María Gutiérrez

Conclusión

This es un poderoso amigo, pero que si no lo controlamos puede convertirse en tu enemigo.

No quiero acabar sin este maravilloso meme de mi compañera Elena Cerezo

If something does not work, ACT. Yes, you! ;) That is why I am now #FrontEnd developer in @kairos_ds constantly absorbing knowledge. Proud #Adalaber.

If something does not work, ACT. Yes, you! ;) That is why I am now #FrontEnd developer in @kairos_ds constantly absorbing knowledge. Proud #Adalaber.