En las próximas entradas de esta serie dedicada a profundizar en los objetos de javaScript comenzaré con patrones de diseño, pero antes conviene que veamos dos conceptos algo abstrusos de javaScript. Uno es el this mutante y otro es el cacao de las funciones callback y los métodos apply, call y bind.
Funciones callback
Como es sabido, las funciones son fragmentos de código encapsulados que realizan una o más acciones. Y las funciones pueden recibir parámetros o argumentos, esto es, datos que se trabajarán dentro de la propia función.
function suma(numero1, numero2) {
console.log(numero1+numero2)
}
suma(3, 2); // 5
En js, dentro de una función podemos ejecutar otra función.
function suma(numero1, numero2) {
console.log(numero1+numero2);
function resta(numero1, numero2) {
console.log(numero1-numero2);
}
resta(3, 2); // 1
}
suma(3, 2); // 5
Pero por limpieza del código, también podríamos haber sacado la segunda función fuera y habérsela pasado a la primera función como argumento, que es una característica de js muy interesante.
function resta(numero1, numero2) {
console.log(numero1-numero2);
}
function suma(numero1, numero2, funcionCallback) {
funcionCallback(numero1, numero2); // 1
}
suma(3, 2, resta);
De hecho, podríamos haberle pasado directamente una función anónima, que es una sintaxis que seguro recuerdan los amantes de jQuery:
function foo(numero1, numero2, bazinga) {
bazinga(numero1, numero2);
};
foo(3, 2, function (numero1, numero2) {
console.log(numero1-numero2); // 1
});
Pues son este tipo de funciones las que en js se denominan funciones callback, que en inglés significa algo así como «llamadas de vuelta».
Un this mutante
Para jugar con funciones que van y vienen también contamos con tres métodos de muy chulos del objeto tipo function, los cuales se basan en el manejo peculiar de this que se hace en javaScript.
En cualquier lenguaje es normal encontrarse con la palabra reservada this para referirse la propia clase donde están declarados métodos y propiedades. Por ejemplo, en php podríamos hacer algo así:
class miClase {
protected $miVariable ="foo";
function devuelveFoo () {
return $this->miVariable; // equivale a decir, "la propiedad miVariable de esta clase"
}
}
En javaScript también existe un this, solo que, como no podría ser de otra manera, se comporta de forma diferente y un tanto alocada. Por ejemplo, fuera de cualquier función y objeto, this hace referencia a window:
console.log(this); // Window
Pero si escribimos algo así, ¡también aparece de nuevo window!
function foo() {
console.log(this); // Window
}
foo();
Sin embargo, a veces sí que parece hacer referencia a la propia función:
var dum = function() {
console.log(this); // dum {}
this.bam = function() {
console.log(this); // dum {bam: function}
}
}
miDum = new dum();
miDum.bam();
Para entender este comportamiento extraño, lo primero que debemos tener en cuenta es que this suele hacer referencia al objeto desde el que se invoca una función o método, es decir, al contexto de la función. Esto se entiende bien si nos fijamos en este ejemplo, donde asignamos la misma función a dos objetos distintos:
function bazinga() {
console.log(this.foo);
}
/* Aquí el this de bazinga hace referencia a window, porque este es objeto desde el que ha sido invocada la función */
console.log(this);
var miObjeto = {
foo: "bar",
metodo: bazinga
}
var otroObjeto = {
foo: "zasprist",
metodo: bazinga
}
/* Aquí this apunta a miObjeto */
miObjeto.metodo(); // bar
/* Aquí this apunta a otroObjeto */
otroObjeto.metodo(); // zasprist
/* y aquí a OtroMas */
var OtroMas = function() {
this.foo = "hey";
this.metodo = bazinga;
}
otroMas = new OtroMas();
otroMas.metodo(); // hey
Si este fuera siempre el comportamiento de this, aunque es algo enrevesado, podríamos manejarlo con cierta soltura. Sin embargo hay una especie de bug, por el que se pierde su referencia cuando se utiliza en una función desde un objeto. Por ejemplo:
var MiClase = function() {
this.foo = "bar";
this.bazinga = function() {
console.log(this.foo); // bar
console.log(this); // MiClase
function yuk() {
console.log(this.foo); // undefined
console.log(this); // Window o0
}
yuk();
}
}
miInstancia = new MiClase();
miInstancia.bazinga();
Para resolver este problema, muchas veces se cachea al principio el valor de this en una variable que, por convención, se suele llamar self o that.
var MiClase = function() {
var self = this;
this.foo = "bar";
this.bazinga = function() {
function yuk() {
console.log(self.foo); // bar
console.log(self); // MiClase
}
yuk();
}
}
Bueno, volveré sobre este tema en próximas entradas porque es particularmente complejo, pero de momento nos basta con entender que this suele apuntar al contexto de la función... salvo que lo cambiemos : ).
apply, call y bind
Con el método call() podemos cambiar el valor de this, que pasa ser el que indiquemos en su primer parámetro. Además, si fuera necesario, podemos pasar los n parámetros que demande la función. En un ejemplo muy sencillo:
function sumar(numero) {
/* this = 2 y numero = 5 */
console.log(this+numero); // 7
}
sumar.call(2, 5);
En otro igual de sencillo:
function convierteMayuscula() {
return this.cadena.toUpperCase();
}
miObjeto = {
cadena: "foo"
}
otroObjeto = {
cadena: "bar"
}
console.log( convierteMayuscula.call(miObjeto) ); // FOO
console.log( convierteMayuscula.call(otroObjeto) ); // BAR
El método apply() es muy parecido, solo que además del valor de this se le puede pasar un array de argumentos. Y esto es muy interesante cuando los desconocemos o carecen de importancia, sobre todo combinado con el objeto arguments, que es una especie de array con todos los argumentos que ha recibido una función.
Por ejemplo, así podríamos calcular la suma de todos los ítems de un array a los que sumamos a su vez 2.
function suma() {
var resultado = 0;
var i = 0;
var len = arguments.length;
for (; i<len; i++) {
resultado += this + arguments[i];
}
return resultado;
}
var numeros = [1, 4, 5];
console.log( suma.apply(2, numeros) ); // 16
Y ya solo nos falta por ver bind(). Dicho de la forma menos liosa que puedo: este método crea una nueva función idéntica a la original, pero en la que podemos definir qué valor tendrá this. En el ejemplo más sencillo que se me ocurre:
function bazinga() {
console.log(this.cantar);
}
/* Aquí bazinga es undefined, ya que no tiene la propiedad cantar */
bazinga();
var miObjeto = {
cantar: "Oh la la!"
};
miObjeto.miMetodo = bazinga.bind(miObjeto);
/* Aquí bazinga es Oh la la!, ya que ahora this sí que está definido, es miObjeto */
miObjeto.miMetodo();
Bind nos permite solucionar algunas situaciones complejas, como las que se producen con el uso de this en los callbacks, pero eso ya guarda relación con los patrones de diseño que comenzaremos a ver en las siguientes entradas. Por hoy está bien, que es tarde :P.
Pingback: react revisitado: 05. Eventos | The Bit Jazz Band