Hoisting en Javascript

26 Dic 2010

Recientemente un viejo colega (@f5inet) me pedía una breve introducción al uso de las variables en Javascript. Explicándole por encima las normas básicas, llegué a uno de los puntos que más confusión despiertan y que son una fuente frecuente de errores para los programadores noveles: el hoisting.

El hoisting («elevación») en Javascript es una de esas particularidades del lenguaje sobre la que la comunidad de desarrolladores no llega a ponerse de acuerdo en si se trata de un bug o una feature.

Para John Resig, manejado correctamente, es un poderoso aliado a la hora de gestionar los ámbitos que alcanzan nuestras variables; para otros como Douglas Crackford, su descuido se convierte inmediatamente en inconsistencias en nuestros códigos y errores potenciales difíciles después de identificar y depurar.

Qué es el hoisting?

En Javascript, cuando se define una variable en el interior de una función, el intérprete interno pasa a ubicarla al comienzo de su contexto (la eleva). Podemos imaginarnos así a nuestras funciones como recipientes de agua hirviendo donde las partículas más pequeñas (las variables) buyen hacia la superficie; el resto de elementos, incluidos los valores de esas variables, son más pesados y se quedan donde están.

Ejemplos

Tomemos el siguiente código:

function foo(){
 bar();
 var x = 1;
}

Pese a que hemos declarado la variable x un punto concreto del código, el intérprete Javascript mueve dicha declaración a lo más alto de su contexto. Internamente, el código se ejecutaría de la siguiente manera:

function foo(){
 var x;
 bar();
 x = 1;
}

La variable es declarada antes que se ejecute cualquier otra acción, lo que puede llegar a ocasionar comportamientos no esperados:

var x = 'Hello World'; // variable global
 
function foo(){
 alert( x ); // esperamos el valor global
 var x = 'New Value'; // redefinimos la variable en contexto local
 alert( x );  // esperamos el nuevo valor local
}
 
foo();

Posiblemente este código no funcione como se deseábamos: el primer alert() no devuelve el valor ‘Hello World’ pese a que la variable había sido declarada anteriormente como global. En su lugar, obtenemos un inesperado undefined. La razón es que la posterior declaración de x, internamente se eleva hasta el inicio de su función (buye), por lo que pasa a estar definida de forma local pero sin un valor aún asignado. El anterior código equivale a este mucho más claro:

var x = 'Hello World';
 
function foo(){
 var x;
 alert( x );
 x = 'New Value';
 alert( x );
}
 
foo();

La recomendación de los expertos es siempre declarar las variables locales al principio de su actual ámbito para evitar errores. De esta forma ganamos además legibilidad del código ya que agrupamos así todas nuestras variables en un mismo lugar permitiéndonos de un vistazo conocer el tipo de datos que esperan:

function foo(){
 var myObj = {};
 var myArray = [];
 var number1, number2, number3;
 var string1, string2, string3;
 
 // Some code here...
}

Como apunte final, aclarar que el término hoisting no está definido dentro del actual ECMAScript, sin embargo es comunmente utilizando para describir el particular comportamiento que Javascript hace de las variables en el interior de las funciones.

Más:

{10} Comentarios.

  1. davidgarsan

    Muy interesante tu blog.
    Lo acabo de descubrir accidentalmente…
    Un remedio de la abuela, la que fuma, para evitar accidentes como el de tu segundo ejemplo es usar el ámbito al referirse a una variable declarada globalmente, fuera de una función.
    En este caso alert(window.x).

    • Carlos Benítez

      Hola David;
      efectivamente, si la idea es referirse a la variable global, podemos acceder a ella a través del objeto window (contexto general del navegador).
      Sin embargo, la idea aquí es no confundirse con el ámbito en el que se declaran las variables para así evitar comportamientos raros 🙂

      Tú ejemplo quedaría:

      var a = 'foo';
      
      (function hola(){
        console.log( window.a ); // foo
        var a = 'bar';
        console.log( a ); // bar
      })();
      

      Un saludo!

  2. davidgarsan

    Bendito namespacing de todos modos…

  3. Jossue Galdamez

    function gracias(){
    var etnassoft = «Carlos Benitez»;
    i= 0;
    for(i; i<5; i++){
    alert("muchas gracias"+ " " + etnassoft);
    }
    }

    gracias();

  4. Esto esta bueno , aun pienso que mi codigo en js es un asco pero espero poco a poco tener un codigo minimalista pero funcional :B

  5. Ba3

    Excelente explicación amigo, gracias 😀

  6. Alberto Pedron

    Muchísimas gracias por tu explicación, todo muy claro y bien explicado.

  7. Carlos

    Muy bien explicado, un saludo.

  8. martin

    Muy buena la explicación, menos mal que en la facu nos enseñaron primero en funcional y luego en imperativo, evitándonos todos estos trastornos …
    Gracias por la buena info 🙂

  9. Jesús Petrona Castro

    La solución a eso es usar la palabra reservada «this», para que digamos a interprete que vamos a usar la variable global.

    alert( this.x ); // esperamos el valor global
    var x = ‘New Value’; // redefinimos la variable en contexto local
    alert( x ); // esperamos el nuevo valor local
    }

    foo();

Deja un comentario

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