> Manuales > Manual de JavaScript

Explicamos un concepto ampliamente usado en Javascript para crear una envoltura para una función, llamada como closure o las siglas IIFE.

IIFE, closures o simplemente envoltura de función en Javascript

Vamos a ponerle nombre a algo que quizás ya conozcas, ya habías usado en Javascript o al menos visto en algún código por ahí. Se trata de las envolturas de funciones que podemos crear para aislar un código fuente y evitar colisiones de variables o espacios de nombres en general.

Yo siempre le he llamado simplemente "envoltura" y en inglés "closure", pero es un patrón que tiene nombre con las siglas IIFE que vienen de "Immediately-invoked function expression". Explicaremos de donde viene y cuál es su utilidad en el mundo de la programación en Javascript.

Este artículo quizás sea un poco avanzado, teniendo en cuenta el momento en el que estamos en el Manual de Javascript, si estás aprendiendo Javascript en estos momentos. No te preocupes si no lo entiendes porque es un conocimiento que puedes dejar para más adelante. Permite apliar prácticas aconsejables para el desarrollo de aplicaciones, pero no son para nada imprescindibles en el desarrollo con Javascript.

Problemática

Todo el mundo conoce ya el riesgo de las variables globales y sabe que las colisiones de nombres a veces ocurren, trayendo consecuencias desastrosas para la ejecución de los programas. Lo peor es que a menudo los motivos de los problemas son difíciles de detectar. Eso unido a que en Javascript acostumbramos a usar librerías con códigos de distintos lugares, produce que en ocasiones esa problemática sea todavía más frecuente.

Por otra parte, en la programación orientada a objetos sabemos que la encapsulación es una de las mecánicas que aportan diversos beneficios al código. Javascript no tiene clases y tampoco puedes crear dentro de objetos variables que sean privadas, por lo que se deben implementar mecanismos alternativos.

Por lo tanto tenemos que buscar un método de crear módulos que sean lo más independientes posibles y donde tengamos un ámbito de las variables propio y restringido.

Nota:En las próximas versiones de Javascript (dentro de poco tendremos ECMA 6) se irán aportando soluciones "nativas" (provistas por el propio lenguaje) a estas situaciones. De momento tenemos que hacerlo un poco "a mano".

Solución

Todo pasa por crear un ámbito propio para tu código, donde las variables que crees existan solo dentro de ese ámbito reducido, y no se puedan acceder desde fuera. Esto en código no es nada difícil de conseguir, pues con Javascript podemos crear ámbitos simplemente con funciones.

Las variables declaradas con "var" dentro de una función son locales a esa función. Sin embargo, estamos exponiendo hacia fuera el propio nombre de la función.

function miFuncion(){
	var x = "hola DesarrolloWeb.com";
}

Esto no es desastroso, pues solo estamos reclamando para nosotros el nombre "miFuncion" al que le estamos asignando una función. Pero se puede mejorar.

Lo conseguimos con funciones anónimas que se auto-invocan. Existen varias maneras de conseguir esto, pero el patrón comúnmente usado es este:

(function(){
	//código de tu función
}());

Eso es lo que significa IIFE y lo que se llama habitualmente como "closure" o envoltura.

Punto y coma "defensivo"

Solo un detalle interesante que encontrarás por ahí. Generalmente colocamos un punto y coma antes del paréntesis inicial. Ésto se hace para conseguir que los paréntesis donde estamos encerrando la función no se tomen como una llamada a una función.

var y = 3;
var x = y
(function(){
    //esto sintácticamente es correcto
}());

Ese código es sintácticamente correcto, a pesar que en la segunda línea no se haya finalizado con un punto y coma, pues sabemos que ";" como final de las sentencias es opcional en Javascript. El problema es que la envoltura de la función se va a tomar como si fueran parámetros enviados a una supuesta función "y". Esto provoca que tu código arroje un error diciendo que un número (y = 3) no es una función.

La solución es colocar un punto y coma antes del closure, lo que se llama un "punto y coma defensivo".

var y = 3;
var x = y
;(function(){
    //Ahora es mejor todavía!
}());

Listo! ahora ya no da error. Podrías pensar que nuestro código podría ser más correcto si le colocamos el punto y coma al final de la segunda línea y tendrías razón. Si fuera tu propio código así lo harías, pero el caso es que no siempre va a ser tu código lo que tengas detrás, pues puedes haber incluido un script creado por otro desarrollador o una librería donde se hayan olvidado de colocar los ";".

Paso de variables

A veces necesitamos pasar variables a nuestro código, para realizar cualquier tipo de operación donde las necesites. Por ejemplo es el caso de la librería jQuery, si es que en ese código vas a usarla. $ es una variable global que te crea la librería, pero lo cierto es que no siempre $ puede significar jQuery, pues esa variable puede haber sido ocupada por otras librerías o códigos Javascript.

En las buenas prácticas de creación de plugins jQuery aprendimos que es ideal pasar la variable jQuery a la función que tenemos en la envoltura y recogerla con el nombre $, para asegurarnos que dentro de esa función $ siempre equivale a jQuery.

;(function($){
    //Ahora $ siempre es jQuery
}(jQuery));

No me extiendo aquí en más explicaciones porque ya las vimos en el artículo Alias personalizado en plugins jQuery. Pero sí quiero comentar algo que comúnmente se usa y que se aprende si lees el código fuente de la propia librería jQuery.

Ese sistema para crear alias de variables lo puedes usar para cualquier variable u objeto que piensas usar dentro de la función, por ejemplo, window o document. Aunque éstos sean objetos globales escribir "document" es muy largo (y no digamos algo como document.forms[0].campo) y si dentro de tu función vas a usarlo varias veces, puedes asignarle un alias personalizado de manera similar a como hiciste con jQuery.

;(function(w,d,o){
    //Ahora w es un alias (shortcut) para window
    //d es un alias de document
    //o es un alias de otraVariableMuyLarga
}(window, document, otraVariableMuyLarga));

Simplemente escribirás menos y tu código será menos pesado en bytes.

Variables privadas

Ahora quiero ahondar sobre la posibilidad de hacer variables encapsuladas en objetos. Lo que se conoce en programación orientada a objetos como variables privadas. Javascript no las implementa.

Para aclararnos veamos este código:

var cuadrado = {
    altura: 2,
    anchura: 3,
    area: function(){
        return this.altura * this.anchura;
    }
}

Tenemos definido un objeto cuadrado (debería haberle llamado rectángulo), con valores en sus propiedades asignados de manera literal. Desde fuera de mi objeto podré acceder a todos sus elementos, tanto propiedades como métodos.

cuadrado.altura=10;
console.log(cuadrado.area());

Ahora veamos este segundo código. Ya usa nuestro método de envoltura para crear un cuadrado y además podemos asignarle los valores de altura y anchura a través de un par de parámetros.

var cuadrado2 = (function(al, an){
    return {
        altura: al,
        anchura: an,
        area: function(){
            return this.altura * this.anchura;
        }
    };
}(5,4));

Lo que pasa es que no hemos adelantado mucho, pues seguimos pudiendo acceder a esas propiedades y métodos.

console.log(cuadrado2.area());
cuadrado2.altura=10;
console.log(cuadrado2.area());

Recuerda que queríamos que esas propiedades fueran privadas, solo accesibles desde mi función. Lo conseguimos con variables locales a la función.

var cuadrado3 = (function(al, an){
    var altura = al;
    var anchura = an;
    return {
        area: function(){
            return altura * anchura;
        }
    };
}(4,6));

En este último caso tenemos un ejemplo bastante ilustrativo de lo que nos permite este patrón IIFE, pues ahora desde fuera no vamos a poder acceder a las propiedades (realmente es que ya no son propiedades del objeto, sino variables locales a la función, sin embargo, dentro del código de los métodos de mi objeto las puedo acceder como si fueran propiedades normales).

console.log(cuadrado3.area());
console.log(cuadrado3.altura); // me dice que undefined

Seguiremos pudiendo acceder al método area(), que nos devolverá el dato correcto. Pero si intentamos acceder a la propiedad "privada" altura, observaremos que Javascript nos dice que es "undefined".

Nota:Para la redacción de partes de este artículo he tomado como referencia la Wikipedia, en la entrada sobre IIFE. Si sigues ese enlace encontrarás a su vez las referencias de la propia Wikipedia que te darán acceso a artículos todavía más técnicos sobre este patrón de programación.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual