La clave para comprender patrones de diseño como IIFE es darse cuenta de que antes de ES6, JavaScript solo presentaba un ámbito de función (por lo tanto, carecía de ámbito de bloque), pasando valores por referencia dentro de cierres. Este ya no es el caso, ya que la versión ES6 de JavaScript implementa el alcance de bloques utilizando las nuevas palabras clave let
y const
.
Contexto de evaluacióneditar
La falta de ámbito de bloque significa que las variables definidas dentro (por ejemplo) de un bucle for tendrán su definición «alzada» en la parte superior de la función de cierre. Evaluar una función que depende de variables modificadas por la función externa (incluida la iteración) puede ser difícil. Podemos ver esto sin un bucle si actualizamos un valor entre definir e invocar la función.
var v, getValue;v = 1;getValue = function () { return v; };v = 2;getValue(); // 2
Aunque el resultado puede parecer obvio al actualizar v
manualmente, puede producir resultados no deseados cuando getValue()
se define dentro de un bucle.
De aquí en adelante, la función pasa v
como argumento y se invoca inmediatamente, preservando el contexto de ejecución de la función interna.
var v, getValue;v = 1;getValue = (function (x) { return function () { return x; };})(v);v = 2;getValue(); // 1
Esto es equivalente al siguiente código:
var v, getValue;v = 1;function f(x) { return function () { return x; };};getValue = f(v);v = 2;getValue(); // 1
El JavaScript eficaz de David Herman contiene un ejemplo que ilustra los problemas del contexto de evaluación dentro de los bucles. Si bien el ejemplo de Herman es deliberadamente enrevesado, surge directamente de la misma falta de alcance de bloque.
Establecer variables privadas y accesoreseditar
Los IIFEs también son útiles para establecer métodos privados para funciones accesibles, al tiempo que se exponen algunas propiedades para su uso posterior. El siguiente ejemplo proviene de la publicación de Alman en IIFEs.
// "counter" is a function that returns an object with properties, which in this case are functions.var counter = (function () { var i = 0; return { get: function () { return i; }, set: function (val) { i = val; }, increment: function () { return ++i; } };})();// These calls access the function properties returned by "counter".counter.get(); // 0counter.set(3);counter.increment(); // 4counter.increment(); // 5
Si intentamos acceder a counter.i
desde el medio ambiente mundial, va a ser indefinido, ya que está incluida dentro de la función invocada y no es una propiedad de counter
. Del mismo modo, si tratamos de acceso i
, se producirá un error, ya que no hemos declarado i
en el entorno global.