Unos días después de haber leído este artículo en un blog he decidido hacer uno para ayudar a terminar a aclarar lo que se comenta, ya que igual que comenté por twitter, no acabo de ver que quede claro ni los ejemplos muy acertados y creo que no he sido del todo claro a través de ese hilo.
¿Qué es exactamente this en JavaScript?
Este concepto ha sido explicado miles de veces y no creo que sea yo quién vaya a aportar nada nuevo al respecto, normalmente lo definimos como el valor del objeto en el que se está definiendo esta función, lo cuál no se puede tomar al pie de la letra teniendo en cuenta que existen los métodos call(), apply() y bind(). Vamos a ver un pequeño ejemplo, pero antes te recomiendo que leas este artículo de Eduard Tomas (@eiximenis) un referente en JavaScript sin duda y explica de una manera mucho más completa y extendida lo comentado anteriormente.
Partimos de este código:
// Simple function
var printText = function(text) {
console.log(text);
}
// JavaScript OOP (prototype pattern)
function Printer(text) {
this.text = text;
}
Printer.prototype.printQuote = function(before, after) {
var quote = before + this.text + after;
printText(quote);
}
Tenemos una función que simplemente muestra la variable que recibe por pantalla (esperamos que sea un texto) y una ‘clase’ cuyo constructor asigna a su propiedad text un texto y que tiene un método llamado ‘printQuote’ que recibe ‘before’ y ‘after’ para unirlos a la propiedad de la ‘clase’ text y mostrarla por pantalla gracias a la función del principio.
Hasta aquí muy sencillo, ahora hagamos algunas pruebas:
var myprinter = new Printer('Lorem ipsum graficum');
myprinter.printQuote('`', '´');
Correcto, lo que va a mostrar por pantalla es `Lorem ipsum graficum´ esto confirma la definición más simple que he dado al principio.
Pero si usamos los métodos call o apply la cosa cambia:
var myprinter2 = new Printer('Call me, maybe!');
myprinter.printQuote.call(myprinter2, '(', ')');
var myprinter3 = new Printer('See you later..');
myprinter.printQuote.apply(myprinter3, ['{', '}']);
Lo que muestra por pantalla (Call me, maybe!) {See you later..} ¿Era lo que esperabas? Estamos pasando como primer parámetro el valor de this.
Un último ejemplo:
var myprinter4 = new Printer('Return to the article :)');
myprinter.printQuote.bind(myprinter4)('----', '----');
¿Y ahora? —-Return to the article :)—- Es un poco más complicado, pero básicamente al utilizar bind se crea una nueva función cuyo contexto va a ser el contexto que le estamos pasando cómo parámetro.
Si no acabas de tener claro esto, te recomiendo que vuelvas al artículo de Eduard mencionado arriba, que leas un poco más sobre call, apply y bind y que juegues con estos ejemplos que he utilizado.
Pero la idea de este artículo es aclarar las dudas que puedas tener respecto al this en TypeScript
¿Qué es exactamente this en TypeScript?
La respuesta es realmente sencilla, exactamente el mismo que el de JavaScript.
Después ‘del titular’ un poco de contexto:
TypeScript está basado en EcmaScript igual que lo está JavaScript (de hecho parece que aunque ahora mismo no lo respete al 100% la idea es acabar convergiendo en alguna de las versiones que salgan), el problema es que muchas veces comparamos lenguajes basados en diferentes versiones de EcmaScript y eso es un error.
Ahora mismo en JavaScript la mayoría de nosotros trabajamos (o al menos desplegamos) código basado en la versión de EcmaScript5, pero TypeScript ya va por la versión EcmaScript6 (conocida por EcmaScript 2015) incluso empiezan a salir cosas de EcmaScript7 (async y await…).
Parece que esto puede conducir a error, incluso a pensar que this se comporta diferente en TypeScript y en JavaScript, pero no lo hace.
¿Y el problema que se plantea en ese artículo?
Pues el problema más que con this es con la fat arrow que hay en EcmaScript6 y que usa desde hace tiempo TypeScript, lo voy a mostrar con los siguientes ejemplos:
Partimos del código anterior un poco modificado (he añadido un tercer parámetro a modo de callback)
// Simple function
var printText = function(text, callback) {
console.log(text);
if(callback) {
callback();
}
}
// JavaScript OOP (prototype pattern)
function Printer(text) {
this.text = text;
}
Printer.prototype.printQuote = function(before, after) {
var quote = before + this.text + after;
printText(quote, function() {
alert('You printed ' + this.text)
});
}
¿Qué crees que va a salir al por pantalla al ejecutar el siguiente código?
var myprinter = new Printer('Lorem ipsum graficum');
myprinter.printQuote('`', '´');
Pues nada menos que un undefined de esos que siempre nos hacen compañía en este gran lenguaje:
Aunque por suerte esto no es una sorpresa para nosotros (después de tanto leer ya somos unos expertos en this, ¿No?) la función anónima que hemos pasado cómo parámetro está definida en el ámbito global, en este caso window, pero vamos a probarlo una vez más por si acaso añadiendo una línea:
var myprinter = new Printer('Lorem ipsum graficum');
window.text = '--- Text in window ---';
// bonus track this.text = '--- Text in window ---';
myprinter.printQuote('`', '´');
Perfecto justo lo esperado 😀
¿Y la fat arrow?
La fat arrow la usamos muchas veces para esas funciones anónimas que pasamos cómo parámetro, más cómodo más limpio y más geek, de modo que haciendo un cambio muy pequeño:
Printer.prototype.printQuote = function(before, after) {
var quote = before + this.text + after;
printText(quote, () => { // The change
alert('You printed ' + this.text)
});
}
Si ejecutamos el primer ejemplo… ya no tenemos undefined
Y esto está pasando porqué una fat arrow no es directamente una función anónima, la podemos explicar cómo es una función que devuelva otra función cuyo contexto es el valor del objeto donde se está llamando esta función (¿Recuerdas el método bind?)
Obtenemos exactamente el mismo resultado (Para esta caso concreto) utilizando un código tal que:
Printer.prototype.printQuote = function(before, after) {
var quote = before + this.text + after;
var callback = function () {
alert('You printed ' + this.text)
}
printText(quote, callback.bind(this));
}
Hasta aquí mis pequeños ejemplos, espero haber aclarado las dudas, en caso de que tuvieras alguna, y te animo a escribir en los comentarios de este artículo cualquier: duda, mejora o aclaración al respecto.
Y por supuesto te recomiendo que sigas leyendo sobre EcmaScript6 y sobre TypeScript 🙂