El valor de this en Javascript: cómo manejarlo correctamente

Introducción

this es un valor complejo en Javascript; un error en el diseño del lenguaje susceptible de causar tanto comportamientos inesperados como potentes estructuras de código reutilizable. Su valor, lejos de ser intuitivo, se determina según la forma en que se invoque a la función que lo encierra, algo que no siempre es correctamente entendido o aprovechado.

Dado que el correcto manejo de this resulta esencial en la programación orientada a objetos, y en definitiva en el desarrollo moderno de aplicaciones, es interesante dedicar unos minutos a revisarlo.

Qué es exactamente this

La palabra clave this tiene en Javascript un comportamiento diferente al de otros lenguajes pero por lo general, su valor hace referencia al propietario de la función que la está invocando o en su defecto, al objeto donde dicha función es un método.

La palabra clave del párrafo anterior es “propietario“.

Cuando no estamos dentro de una estructura definida, esto es un objeto con métodos, el propietario de una función es siempre el contexto global. En el caso de los navegadores web, tenemos que recordar que dicho objeto es window:

console.log( this === window );  // true
 
function test(){
  console.log( this === window);
}
 
test(); // true

Accediendo a los valores de un objeto desde el propio objeto

Este concepto de propietario puede inducir a errores. Pensemos en un objeto con una serie de propiedades:

var myApp = {
  name : 'Megan',
  lastName : 'Fox',
  birthDate : '16/05/1986',
  isPretty : true
};
 
console.log( myApp.name ); // Megan
console.log( myApp.isPretty ); // true

Supongamos ahora que necesitamos otra propiedad más ‘dinámica’ que participe de los valores asignados a cualquier otra. Por ejemplo, queremos un ‘completeName‘ que concatene ‘name‘ y ‘lastName‘. Parece que este un claro ejemplo de uso para this:

var myApp = {
  name : 'Megan',
  lastName : 'Fox',
  completeName : this.name + this.lastName
}

Aunque parece coherente, cuando pasamos a comprobarlo vemos que el resultado no es el esperado:

console.log( myApp.completeName ); // undefined

El problema aquí es que this no está apuntando al objeto como cabría esperar, sino que está buscando la referencia fuera, en el contexto global (window).

Para obtener el resultado esperado tenemos que aplicar un patrón de invocación que modifique al propietario desde el que se invoca el this

Patrón de invocación por método

En Javascript, existen varias formas de llamar a las funciones desde nuestro código; concretamente, podemos enumerar hasta cinco diferentes. La elección de una u otra responde al flujo de nuestro programa pero básicamente, difieren en el contexto que queramos aplicar a cada una. O lo que es lo mismo, al valor que queramos que la función asigne a la referencia this.

En el desarrollo de aplicaciones modernas, el patrón más recurrente es el de invocación por método: una función es almacenada como propiedad de un objeto convirtiéndose así en lo que denominamos un método.

Cuando llamamos (invocamos) a un método, this hace refencia al propio objeto:

var myApp = {
  name : 'Megan',
  lastName : 'Fox',
  completeName : function(){
    return this.name + ' ' + this.lastName;
  }
}
 
console.log( myApp.completeName() ); // Megan Fox

En esta ocasión, si podemos comprobar como this apunta al propio objeto y busca la propiedades ‘name‘ y ‘lastNamedentro en lugar de remontarse hasta el contexto global.

Pero como vimos en el caso anterior, no siempre el uso de this es intuitivo. Consideremos la siguiente estructura:

var myApp = function(){
  var name = "World"
  var sayHello = function(){
    console.log( 'Hello, ' + this.name );
  };
  sayHello(); // Invoke the function
};
 
myApp(); // Hello,

¿A dónde está apuntando this en este caso? Como la función no es ahora la propiedad de un objeto, this apunta de nuevo al global (window). Esto es un error en el diseño del lenguaje ya que, de comportarse como se espera, this debería apuntar a la función contenedora (que no deja de ser su propietaria).

Este comportamiento lo podemos comprobar si creamos una variable global con aquel nombre por el que estamos preguntando:

var name = "Strange World";
var myApp = function(){
  var name = "World"
  var sayHello = function(){
    console.log( 'Hello, ' + this.name );
  };
 
  sayHello();
 
};
 
myApp(); // Hello, Strange World

La consecuencia de este error es que un método no puede utilizar funciones internas que la ayuden a hacer su trabajo porque éstas, no tiene acceso a sus propiedades.

Para resolver este problema, podemos recurrir a una solución muy simple: definir dentro de nuestra función contenedora una nueva variable que cachee el valor de this para que así esté disponible desde cualquier otra función anidada que lo precise. Por convención, el nombre de esta variable es that:

var myApp = function(){
  var that = this; // Work around!
  var name = "World"
  var sayHello = function(){
    console.log( 'Hello, ' + that.name );
  };
 
  sayHello();
};
 
myApp(); // Hello, World

NOTA: Últimamente, la convención que propuso Douglas Crockford está siendo revisada y son muchos los desarrolladores que prefieren el término self para referirse al objeto cacheado.

Sobreescribiendo el valor de this en tiempo de ejecución

Una de las características consideradas avanzadas dentro del lenguaje Javascript es la posibilidad de reescribir el valor de this en tiempo de ejecución consiguiendo así una modularidad y reusabilidad extrema.

Más arriba comentamos que existen varias formas de invocar funciones en Javascript donde la elección entre una u otra depende del valor que queramos asignar a this.

Consideremos el siguiente codigo:

var person1 = {
  name : 'Angelina', 
  lastName : 'Jolie',
  birthDay : '04/06/1975',
  lastMovie : 'The Tourist'
};
 
var person2 = {
  name : 'Scarlett',
  lastName : 'Johansson',
  birthDay : '22/11/1984',
  lastMovie : 'We bought a Zoo'
}
 
var printName = function(){
  console.log( this.name + ' ' + this.lastName );
}

Si tratamos de ejecutar sin más la función printName, ya sabemos que el this apunta al objeto global window e irá ahí a buscar las propiedas name y lastName.

En una arquitectura modular, lo ideal es que esa función pueda ser reaprovechada y que nos permita imprimir los datos de cualquier objeto que cumpla con los requisitos. Para ello, podemos cambiar el valor de this en tiempo de ejecución con el fin de que apunte a ese objeto en cuestión. Para ello, contamos con las funciones nativas apply y call:

printName.call( person1 ); // Angelina Jolie
printName.call( person2 ); // Scarlett Johansson
 
printName.apply( person1 ); // Angelina Jolie
printName.apply( person2 ); // Scarlett Johansson

El resultado es el mismo: hemos ejecutado la función printName dentro del contexto de cada uno de los objetos que hemos pasado como argumento.

La diferencia entre cada uno de estas funciones es que la segunda de ellas, apply, acepta un array como argumento, lo que permite una flexibilidad aún mayor a la hora de plantear código reusable.

Pensemos en una función que nos permita actualizar datos dentro de nuestros objetos:

var updatePerson = function(name, lastName, birthDay, lastMovie){
  this.name = name;
  this.lastName = lastName;
  this.birthDay = birthDay;
  this.lastMovie = lastMovie;
}
 
updatePerson( person1, 'Angelina', 'Jolie', '04/06/1975', 'Kung Fu Panda 2');

Las limitaciones de call se hacen rápidamente evidentes cuando queremos escribir código que no conoce (o no debe conocer) el número de argumentos que la función necesita.

Tomemos por ejemplo una función que siga el conocido paradigma del ‘dispatcher‘:

var dispatch = function(person, method, args){
  method.apply( person, args );
}
 
dispatch( person1, printName );
dispatch( person2, update, ['Scarlett', 'Johansson', '22/11/1982', 'The Avengers'] );

En este caso, el poder jugar con el valor de this y además asociar una serie de datos a través de un array, permite una poderosa flexibilidad a la hora de escribir código reutilizable.

Conclusión

El valor de this ha sido siempre difícil de manejar en Javascript. Las pequeñas diferencias que presenta frente a otros lenguajes así como un error en su diseño, lo hacen francamente especial. Sin embargo, esa peculiaridad, es precisamente la que permite crear un tipo de código muy versátil una vez que conocemos sus secretos.

Manejarlo correctamente es una parte esencial dentro del desarrollo de aplicaciones complejas en Javascript.

Acerca de Carlos Benítez

Programador Web y arquitecto Javascript. Fundador de www.etnassoft.com y OpenLibra, la Biblioteca Libre Online
Esta entrada fue publicada en Javascript y etiquetada , , , , , , . Guarda el enlace permanente. Sígueme en Twitter

Últimos libros gratuitos añadidos a OpenLibra

{13} Comentarios.

  1. 12Ene2012/
    11:58 h /
    alejandro

    No hubiera podido explicarlo mejor. Excelente entrada!!

  2. 12Ene2012/
    13:42 h /

    Exelente articulo

  3. 13Ene2012/
    1:21 h /

    Explicación de la Madre 😀 Excelente articulo

  4. 16Ene2012/
    8:31 h /

    Hola Carlos, en el apartado “Accediendo a los valores de un objeto desde el propio objeto”, símplemente es que está mal definida la propiedad, en realidad habría que escribirla (para que sea una propiedad como se pretende) así:

    var foo = {
        name: 'Megan',
        lastName: 'Fox',
        get completeName() {
            return this.name + ' ' + this.lastName;
        }
    };
    

    en este caso sería de “sólo lectura”, si se desea que sea asignable, podría añadirse algo como:

        set completeName(_completeName) {
            var mx = /^([^ \t]+)[ \t]?(.*)$/.exec(_completeName);
            this.name = mx[1];
            this.lastName = mx[2];
        }
    

    ahora podemos hacer algo como

    console.log(foo.completeName); //Megan Fox
    
    foo.completeName = "Meganic Foxic";
    console.log(foo.completeName); //Meganic Foxic
    

    Saludos ¡y gracias por los post!

    • 16Ene2012/
      9:25 h /

      Gracias por el aporte Jose Juan 😉
      Efectivamente, hay que recordar que Javascript incorporó la posibilidad de hacer ‘getters’ en su versión 1.8.5 y que cualquier navegador que interprete correctamente el ES5 permitirá estas operaciones.

      Desgraciadamente, como tantas otras veces, tenemos que tener en cuenta que en los navegadores más antiguos, esta solución no funciona. Estaríamos hablando de IE6, IE7 e IE8.

      No obstante, como aquí hablamos del lenguaje y no de sus implementaciones, tu comentario tiene todo el sentido.

      Saludos!

  5. 17Ene2012/
    16:18 h /
    pmp
    var name = "Strange World";
    var myApp = function(){
      var that = this; // Work around!
      var name = "World"
      var sayHello = function(){
        console.log( 'Hello, ' + that.name );
      };
     
      sayHello();
    };
     
    myApp(); // Hello, World
    

    La ejecución de myApp(); va a seguir dando “Hello, Strange World”, ya que ‘var name= “World”‘ está declarada como variable “privada”.
    Si quisieramos un “Hello, World” en ese ejemplo, deberíamos hacer un console.log(‘Hello, ‘ + name);.

    Si me equivoco, corregidme.

  6. 20Ene2012/
    23:21 h /

    Hola!!

    Primero de todo, felicitarte por tu blog (no lo conocia y lo he devorado en poco menos de un mes), y por “tu culpa” he tenido que rediseñar un par de veces mi proyecto.(Los articulos sobre patrones me han sido de gran utilidad, muchas gracias)

    Mi proyecto es cMox, un framework para desarrollar juegos en HTML5(Canvas)/Javascript adaptados tanto a la web como a los dispositivos moviles (Phonegap). He usado un patron parecido a Jquery para desarrollar la biblioteca cMox, y con esta programar mi primer juego que ya está disponible online o para descargar en Android (Proximamente iPhone)

    http://www.timemox.com/juegos.php

    La principal dificultad ha sido optimizar el rendimiento del refresco de Canvas para dispositivos moviles poco potentes (PC=40FPS, movil=12FPS). Al final, he consiguido que funcione a velocidades razonables (20-25 fps) usando lo que yo llamo ‘DobleCanvas’ (Dos Canvas superpuestos, en el primero dibuja objetos estaticos y en el segundo los dinamicos)

    Ahora, con cMox medio encarrilado, espero realizar juegos con cierta frecuencia, y en cuanto corrija algunos bugs, liberar cMox y hacer un manual de uso.

    Muchas gracias!!

  7. 24Mar2012/
    17:41 h /
    Xavier Lesa

    Hola excelente post!
    ahora tengo algunas dudas, porque al crear una variable sin “expresamente crearla”, ésta parece formar parte del objeto al que que la contiene o sea:

    var myApp = function(){
    world_type = ” Cruel ” // no-expresamente un var
    var name = “World”
    var sayHello = function(){
    console.log( ‘Hello, ‘ + this.world_type + this.name );
    };
    sayHello(); // Invoke the function
    };

    myApp(); // Hello, Cruel

    bueno, la variable world_type parece “pertenecer” al objeto (Function) contenedor, esto es un error de diseño propio de Javascript o algún otro punto oscuro “magico” de Javascript 🙂

    Gracias !!!

  8. 05Mar2013/
    21:41 h /
    Gerardo

    MEGAN FOX jajaja buen articulo amigo

  9. 08Abr2013/
    11:57 h /
    Derick

    ¡Muy buen artículo!

    Para completar los casos en los que se puede utilizar this agreagaría el caso de invocación por constructor. En este caso, this hace referencia al nuevo objeto que retorna dicha invocación. Ej:

    function Coordenada() {
      this.x = 0;
      this.y = 0;
    }
    
    var origen = new Coordenada():
    console.log(origen);
    

    Un saludo.

  10. 06Jun2013/
    14:46 h /

    Excelente.
    Bien explicado y bien documentado.

  11. 04Mar2016/
    16:31 h /
    Robin

    Excelente aporte Man!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Licencia Creative Commons 3.0

®Copyright 2016. Cotenido web bajo licencia Creative Commons 3.0

Códigos bajo licencias MIT y GPL. Ver página de licencias para más información