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

12 Ene 2012

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.

Más:

{18} Comentarios.

  1. alejandro

    No hubiera podido explicarlo mejor. Excelente entrada!!

  2. jesisonp

    Exelente articulo

  3. Alberto

    Explicación de la Madre 😀 Excelente articulo

  4. josejuan

    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!

    • Carlos Benítez

      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. 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. Oriol Faura

    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. 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. Gerardo

    MEGAN FOX jajaja buen articulo amigo

  9. 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. Pedro Herrarte

    Excelente.
    Bien explicado y bien documentado.

  11. Andres Luque

    Hola,

    Yo uso jquery con mis aplicaciones, y para efectos del this en el caso de un evento el siempre va a tomar el elemento que disparo el evento, sin embargo en un caso particular, necesito que el this apunte al padre de este elemento.
    Ejemplo en una tabla tengo varias filas y hay una celda en especial con un icono, cuando se dispare el evento clic sobre este icono necesito que this haga referencia es al elemento tr padre de ese icono.

    Esto es posible, he realizado algunas pruebas pero en si no he logrado hacerlos, puedo hacer que el contexto me cambie a window pero no al padre.

    Gracias

  12. Juana

    Estaba un poco perdida con el tema del this, pero ahora lo he entendido de maravilla.
    Muchas gracias!

  13. israel perez

    Hola a todos, sólo un comentario en el ejemplo:

    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

    hay un error pues that=this apunta al ambito global y no a myApp el cual constituye una variable y no un objeto, al hacer la prueba la salida es ‘Hello, Strange World’ tal y como lo menciona PMP. Ojo en el momento de asignarle variable a ‘that’, ‘this’ apunta al objeto global pues es el propietario de ‘myApp’
    saludos

  14. israel perez

    En respuesta a Javier Lesa (3 años despues), cuando una varoiable no se declara con la palabra reservada ‘var’ automaticamente forma parte del ambito global, por lo tanto y como en tu ejemplo ‘this’ apunta ala mabito global, tu salida muestra el valor de ‘world_type’ y no muestra el valor del ‘this.name’ pues en el ambito global no existe una variable denominada ‘name’
    saludos

  15. mikel

    Excelente aporte, claro conciso y bien explicado. Se agradece encontrar respuestas como esta en la busqueda desoluciones.

    Animo para continuar con colaboraciones como esta

  16. Robin

    Excelente aporte Man!

Deja un comentario

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