En el artículo anterior aprendimos qué es (y qué no es) this en JavaScript. Por si no te acuerdas:
this es una referencia que se crea cuando una función es invocada, no declarada. El valor de esa referencia depende al 100% del lugar en la que esa invocación se realice, llamado call-site.
Además, revisamos uno por uno los escenarios más comunes donde el uso de this parece confuso si no eres consciente de cuáles son las leyes que gobiernan su comportamiento.
De esta forma, vimos que en algunas circunstancias el valor de this cambia o, directamente, se pierde, examinando conceptos como default binding o implicit binding.
// Default binding function miFuncion() { console.log(this); } miFuncion(); // global object o undefined en strict-mode // Implicit binding function miFuncion() { console.log(this.a); } var objeto = { a: 'Hola mundo' miFuncion: miFuncion, }; objeto.miFuncion(); // Hola mundo
Explicit binding
Ahora bien, ¿existe alguna forma de forzar a una función a que, cuando sea invocada, use un valor específico de this?. La respuesta es si, todas las funciones en JavaScript tienen acceso a los métodos nativos apply
y call
, respectivamente.
Al invocar la función ambas toman como parámetro el valor de this que deseamos, por eso se llama explicit binding. Aquí tienes un ejemplo:
function saludar() { console.log('Hola, me llamo ' + this.nombre) } var persona = { nombre: 'Juan', } var nombre = 'Andrés'; saludar(); // "Hola, me llamo Andrés" saludar.call(persona); // "Hola, me llamo Juan"
Como puedes ver, con call
invocamos la función saludar
forzando el objeto persona
como valor de this.
Por si te estás preguntando qué diferencias hay entre call y apply: ninguna a efectos de this. Si que se comportan de forma diferente a la hora de recibir parámetros.
A pesar de que con call
o apply
podemos de alguna forma forzar el valor de this en la invocación de la función, esto no resuelve por si sólo el problema que vimos anteriormente (perder la referencia a this). Considera esto:
function saludar() { console.log('Hola, me llamo ' + this.nombre) } var persona = { nombre: 'Juan', saludar: saludar, } function ejecutar(funcion) { // "Aquí perdemos la referencia a this, incluso con call" funcion(); } var nombre = "Andrés"; ejecutar.call(persona, persona.saludar); // "Hola, me llamo Andrés"
La referencia se pierde ya que, como ya sabes, lo importante no es donde está definida la función, sino desde dónde se invoca (call-site). En este caso es una simple función declarada, por lo que el default binding toma el control.
Hard binding
Existe una variante del binding explícito que consigue unir de forma inalterable un contexto de this a la invocación de una función sin importar dónde se haga (call-site) esta:
function saludar() { console.log('Hola, me llamo ' + this.nombre); } var persona = { nombre: 'Juan', saludar: saludar, }; function unir() { saludar.call(persona); } var nombre = "Andrés"; unir(); // "Hola, me llamo Juan" setTimeout(unir, 2000); // "Hola, me llamo Juan" (function() { unir(); // "Hola, me llamo Juan" })();
Como puedes comprobar, da igual desde qué lugar invoquemos la función, el valor de this ha quedado fuertemente unido a la función, de ahí que lo llamemos Hard Binding.
El uso del hard binding es tan común en JavaScript, que las funciones disponen de un método bind
nativo. bind
retorna una función lista para ser invocada y con su valor de this unido de forma indisoluble al contexto que indiquemos:
function saludar() { console.log('Hola, me llamo ' + this.nombre); } var persona = { nombre: 'Juan', }; var saludo = saludar.bind(persona); saludo(); // "Hola, me llamo Juan"
Conclusión
Durante dos artículos hemos examinado los escenarios más comunes de invocación a funciones, revisando el valor de this en cada uno de ellos. Como ahora ya sabes, todo depende del lugar donde se realice esa invocación.
De esta forma, ya sea desde el ámbito global, a través de una llamada de construcción, como método de un objeto o utilizando los métodos bind
, call
y/o apply
, el valor de this no volverá —espero— a sorprenderte.