Closures

  • Enlace amigable (slug) de la revisión: JavaScript/Guide/Closures
  • Título de la revisión: Closures
  • Id de la revisión: 335775
  • Creada:
  • Creador: hjoaco
  • ¿Es la revisión actual? No
  • Comentario

Contenido de la revisión

Closures son considerados como una función avanzada en JavaScript, pero la comprensión es esencial para dominar el language.

Considere la siguiente función:

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();  

La función init()  crea una variable local llamada name, a continuación, define una función denominada displayName(). displayName() es una función interna — esta definida dentro de init(), y sólo está disponible en el cuerpo de esa función. displayName() no tiene ninguna variable local propia, pero vuelve a utilizar la variable name declarada en la función externa.

Esto funciona bien — intenta correr el código para ver qué pasa. Este es un ejemplo de alcance funcional: en JavaScript, el ámbito de una variable se define por su ubicación dentro del código fuente, y funciones anidadas tienen acceso a las variables declaradas en su ámbito externo.

Ahora considere el siguiente ejemplo:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc(); 

Si se ejecuta este código tendrá exactamente el mismo efecto que el ejemplo anterior de init(): el string "Mozilla" se mostrará en un cuadro de alerta de JavaScript. Que es diferente — y interesante — es que la función interna displayName() fue devuelta por la función exterior antes de ser ejecutado.

Que el código funciona todavía puede parecer intuitivo. Normalmente, las variables locales dentro de una función sólo existen para la duración de la ejecución de dicha función. Una vez que makeFunc() haya terminado de ejecutarse, es razonable esperar que la variable name variable ya no será necesaria. Dado que el código funciona como se esperaba, esto obviamente no es el caso.

La solución a este rompecabezas es que myFunc se ha convertido en un closure. Un closure es un tipo especial de objeto que combina dos cosas: una función, y el entorno en que se creó esa función. El entorno está formado por las variables locales que estaban dentro del alcance en el momento que se creó el closure. En este caso, myFunc es un closure que incorpora tanto la función displayName y el string "Mozilla" que existían cuando se creó el closure.

Este es un ejemplo un poco más interesante — una función makeAdder:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

print(add5(2));  // 7
print(add10(2)); // 12 

En este ejemplo, hemos definido una función makeAdder(x) que toma un argumento único x y devuelve una nueva función. La función devuelve un único argumento y, y devuelve la suma de x + y.

En esencia, el makeAdder es una fábrica de función — crea las funciones que pueden añadir un valor específico a su argumento. En el ejemplo anterior utilizamos nuestra fábrica de función para crear dos nuevas funciones — uno que agrega 5 a su argumento, y uno que agrega 10.

add5 y add10 son ambos closures. Que comparten la misma definición de cuerpo de función, pero almacenan diferentes entornos. En el entorno add5's, x es 5. en cuanto a add10 se refiere, x es 10.

Closures prácticos

¿Esa es la teoría de la manera — pero son los closures realmente utiles? Vamos a considerar sus implicaciones prácticas. Un closure le permite asociar algunos datos (los entornos) con una función que opera sobre esos datos. Esto tiene evidentes paralelismos con programación orientada objectos, donde objetos nos permiten asociar algunos datos (propiedades del objeto) con uno o más métodos.

En consecuencia, puede utilizar un closure en cualquier lugar que normalmente puede utilizar un objeto con sólo un simple método.

Situaciones donde desee hacer esto son particularmente comunes en la web. Gran parte del código que se escribe en la web JavaScript está basado en el evento — definir algunos comportamiento y, a continuación, conéctelo a un evento que es desencadena por el usuario (como un click o pulsación de una tecla). Nuestro código generalmente se adjunta como una devolución de llamada 'callback': una simple función que se ejecuta en respuesta al evento.

Aquí está un ejemplo práctico: Supongamos que queremos añadir algunos botones a una página que ajustar el tamaño del texto. Una manera de hacer esto es especificar el tamaño de fuente del elemento body en píxeles y, a continuación, en conjunto el tamaño de los demás elementos de la página (como encabezados) utilizando la unidad relativa em:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}
h2 {
  font-size: 1.2em;
} 

Nuestros botones de tamaño de texto interactivo pueden cambiar la propiedad de tamaño de fuente del elemento body, y los ajustes serán recogidos por otros elementos de la página gracias a las unidades relativas.

Aquí está el código JavaScript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14 y size16 ahora son funciones que cambia el tamaño del texto del cuerpo de 12, 14, y 16 pixels, respectivamente. Nosotros podemos unirlos a los botones (en este caso enlaces) como sigue:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a> 

{{ JSFiddleLink("vnkuZ") }}

 

Emulando los métodos privados con closures

Lenguajes como Java ofrecen la posibilidad de declarar métodos privados, lo que significa que sólo puede ser llamados por otros métodos en la misma clase.

JavaScript no proporciona una forma nativa de hacer esto, pero es posible emular métodos privados utilizando closures. Los métodos privados no son sólo útiles para restringir el acceso al código: también proporcionan una poderosa manera de administrar el espacio de nombres global 'namespace', manteniendo los métodos no esenciales de saturar la interfaz pública a su código.

Así es cómo definir algunas de las funciones públicas que pueden acceder a funciones privadas y variables, utilizando closures que también se conoce  como el module pattern:

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */ 

Hay mucho que hacer aquí. En los ejemplos anteriores cada closure ha tenido su propio entorno; aquí creamos un entorno único que es compartido por tres funciones: Counter.increment, Counter.decrement y Counter.value.

El entorno compartido se crea en el cuerpo de una función anónima, que es ejecutado tan pronto como se ha definido. El entorno contiene dos elementos privados: una variable llamada privateCounter y una función llamada changeBy. Ninguno de estos elementos privados puede accederse directamente desde fuera de la función anónima. En su lugar, se debe acceder por las tres funciones públicas que se devuelven desde el contenedor anónimo.

Esas tres funciones públicas son closure que comparten el mismo entorno. Gracias al alcance léxico de Javascript's, cada uno de ellos tienen acceso a la variable privateCounter y a la función changeBy.

Usted notará que estamos definiendo una función anónima que crea un contador, y luego llamamos inmediatamente y asignamos el resultado a la variable Counter. Podríamos almacenar esta función a una variable independiente y utilizarlo para crear varios contadores.

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */ 

Observe cómo cada uno de los dos contadores mantiene su independencia del otro. Su entorno durante la llamada de la función makeCounter() es diferente cada vez. El closure variable privateCounter contiene una instancia diferente cada vez.

Utilizar closure de este modo proporciona una serie de beneficios que se asocian normalmente con encapsulación y ocultación de datos concreto de programación orientada a objetos.

Creando closures en loops: Un error común

Antes de la introducción de let keyword en JavaScript 1.7, un problema común con closures ocurre cuando se crearon dentro de un bucle 'loop'. Considere el siguiente ejemplo:

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp(); 

{{ JSFiddleLink("v7gjv") }}

El array helpText define tres consejos útiles, cada uno asociado con el ID de un campo de entrada en el documento. El bucle recorre estas definiciones, enlazar un evento onfocus a cada uno que muestra el método de ayuda asociada.

Si tratas de este código, verá que no funciona como se esperaba. No importa qué campo se centrar en, se mostrará el mensaje acerca de su edad.

La razón de esto es que las funciones asignadas a onfocus son closure; que consisten en la definición de la función y el entorno capturado desde el alcance de la función setupHelp. Se han creado tres closures, pero cada uno comparte el mismo entorno único. Por el momento que se ejecutan las devoluciones de llamada onfocus, el bucle ha seguido su curso y la variable de elemento (compartida por todos los tres closure) ha quedado apuntando a la última entrada en la lista del helpText.

En este caso, una solución es utilizar más closure: en particular, con una fábrica de función como se ha descrito anteriormente:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp(); 

{{ JSFiddleLink("v7gjv/1") }}

This works as expected. Rather than the callbacks all sharing a single environment, the makeHelpCallback function creates a new environment for each one in which help refers to the corresponding string from the helpText array.

Performance considerations

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called the methods would get reassigned (that is, for every object creation).

Consider the following impractical but demonstrative case:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

The previous code does not take advantage of the benefits of closures and thus should instead be formulated:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

Or as follows:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See Details of the Object Model for more details.

{{ autoPreviousNext("JSGChapters") }}

Fuente de la revisión

<p>Closures son considerados como una función avanzada en JavaScript, pero la comprensión es esencial para dominar el&nbsp;language.</p>
<p>Considere la siguiente función:</p>
<div style="width:auto;overflow:hidden;">
  <pre class="brush: js">
function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init(); &nbsp;</pre>
</div>
<p>La función&nbsp;<code>init()</code>&nbsp;&nbsp;crea una variable local llamada&nbsp;<code>name</code>,&nbsp;a continuación, define una función denominada&nbsp;<code>displayName()</code>. <code>displayName()</code>&nbsp;es una función interna — esta definida dentro de&nbsp;<code>init()</code>,&nbsp;y sólo está disponible en el cuerpo de esa función. <code>displayName()</code>&nbsp;no tiene ninguna variable local propia,&nbsp;pero vuelve a utilizar la variable&nbsp;<code>name</code>&nbsp;declarada en la función externa.</p>
<p>Esto funciona bien&nbsp;— intenta correr&nbsp;el código para ver qué pasa.&nbsp;Este es un ejemplo de alcance funcional:&nbsp;en JavaScript, el ámbito de una variable se define por su ubicación dentro del código fuente,&nbsp;y funciones anidadas tienen acceso a las variables declaradas en su ámbito externo.</p>
<p>Ahora considere el siguiente ejemplo:</p>
<pre class="brush: js">
function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();&nbsp;</pre>
<p>Si se ejecuta este código tendrá exactamente el mismo efecto que el ejemplo anterior de&nbsp;<code>init()</code>: el string "Mozilla"&nbsp;se mostrará en un cuadro de alerta de JavaScript.&nbsp;Que es diferente&nbsp;— y&nbsp;interesante&nbsp;—&nbsp;es que la función interna&nbsp;<code>displayName()&nbsp;</code>fue devuelta por la función exterior antes de ser ejecutado.</p>
<p>Que el código funciona todavía puede parecer <span style="line-height: 21px;">intuitivo</span>.&nbsp;Normalmente, las variables locales dentro de una función sólo existen para la duración de la ejecución de dicha función.&nbsp;Una vez que&nbsp;<code>makeFunc()</code>&nbsp;haya terminado de ejecutarse,&nbsp;es razonable esperar que la variable&nbsp;name variable&nbsp;ya no será necesaria.&nbsp;Dado que el código funciona como se esperaba, esto obviamente no es el caso.</p>
<p>La solución a este rompecabezas es que&nbsp;<code>myFunc</code>&nbsp;se ha convertido en un&nbsp;<em>closure</em>. Un closure&nbsp;es un tipo especial de objeto que combina dos cosas:&nbsp;una función,&nbsp;y el entorno en que se creó esa función.&nbsp;El entorno está formado por las variables locales que estaban dentro del alcance en el momento que se creó el&nbsp;closure.&nbsp;En este caso, <code>myFunc</code>&nbsp;es un closure&nbsp;que incorpora tanto la función&nbsp;<code>displayName</code>&nbsp;y el string "Mozilla"&nbsp;que existían cuando se creó el&nbsp;closure.</p>
<p>Este es un ejemplo un poco más interesante&nbsp;—&nbsp;una función&nbsp;<code>makeAdder</code>:</p>
<pre class="brush: js">
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

print(add5(2));  // 7
print(add10(2)); // 12&nbsp;</pre>
<p>En este ejemplo, hemos definido una&nbsp;función <code>makeAdder(x)</code>&nbsp;que toma un argumento único&nbsp;<code>x</code>&nbsp;y devuelve una nueva función.&nbsp;La función devuelve un único argumento&nbsp;<code>y</code>, y&nbsp;devuelve la suma de&nbsp;<code>x</code>&nbsp;+&nbsp;<code>y</code>.</p>
<p>En esencia, el&nbsp;<code>makeAdder&nbsp;</code>es una fábrica de función&nbsp;—&nbsp;crea las funciones que pueden añadir un valor específico a su argumento.&nbsp;En el ejemplo anterior utilizamos nuestra fábrica de función para crear dos nuevas funciones&nbsp;—&nbsp;uno que agrega&nbsp;5&nbsp;a su argumento, y uno que agrega 10.</p>
<p><code>add5</code>&nbsp;y&nbsp;<code>add10</code>&nbsp;son ambos&nbsp;closures.&nbsp;Que comparten la misma definición de cuerpo de función, pero almacenan diferentes entornos. En el entorno&nbsp;<code>add5</code>'s, <code>x</code>&nbsp;es 5. en cuanto a <code>add10</code>&nbsp;se refiere, <code>x</code>&nbsp;es 10.</p>
<h2 id="Practical_closures">Closures prácticos</h2>
<p>¿Esa es la teoría de la manera&nbsp;— pero son los closures realmente utiles?&nbsp;Vamos a considerar sus implicaciones prácticas.&nbsp;Un closure&nbsp;le permite asociar algunos datos&nbsp;(los entornos)&nbsp;con una función que opera sobre esos datos.&nbsp;Esto tiene evidentes paralelismos con programación orientada&nbsp;objectos,&nbsp;donde objetos nos permiten asociar algunos datos&nbsp;(propiedades del objeto)&nbsp;con uno o más métodos.</p>
<p>En consecuencia, puede utilizar un&nbsp;closure&nbsp;en cualquier lugar que normalmente puede utilizar un objeto con sólo un simple método.</p>
<p>Situaciones donde desee hacer esto son particularmente comunes en la web.&nbsp;Gran parte del código que se escribe en la web JavaScript está basado en el evento&nbsp;—&nbsp;definir algunos comportamiento y,&nbsp;a continuación,&nbsp;conéctelo a un evento que es desencadena por el usuario&nbsp;(como un click o pulsación de una tecla).&nbsp;Nuestro código generalmente se adjunta como una devolución de llamada 'callback':&nbsp;una simple función que se ejecuta en respuesta al evento.</p>
<p>Aquí está un ejemplo práctico:&nbsp;Supongamos que queremos añadir algunos botones a una página que ajustar el tamaño del texto.&nbsp;Una manera de hacer esto es especificar el tamaño de fuente del elemento body en píxeles y,&nbsp;a continuación,&nbsp;en conjunto el tamaño de los demás elementos de la página&nbsp;(como encabezados)&nbsp;utilizando la unidad relativa em:</p>
<pre class="brush: css">
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}
h2 {
  font-size: 1.2em;
}&nbsp;</pre>
<p>Nuestros botones de tamaño de texto interactivo pueden cambiar la propiedad de tamaño de fuente del elemento body,&nbsp;y los ajustes serán recogidos por otros elementos de la página gracias a las unidades relativas.</p>
<p>Aquí está el código JavaScript:</p>
<pre class="brush: js">
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
</pre>
<p><code>size12</code>, <code>size14</code>&nbsp;y&nbsp;<code>size16</code>&nbsp;ahora son funciones que cambia el tamaño del texto del cuerpo de&nbsp;12, 14, y 16 pixels, respectivamente.&nbsp;Nosotros podemos unirlos a los botones&nbsp;(en este caso enlaces)&nbsp;como sigue:</p>
<pre class="brush: js">
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
</pre>
<pre class="brush: html">
&lt;a href="#" id="size-12"&gt;12&lt;/a&gt;
&lt;a href="#" id="size-14"&gt;14&lt;/a&gt;
&lt;a href="#" id="size-16"&gt;16&lt;/a&gt; 
</pre>
<p>{{ JSFiddleLink("vnkuZ") }}</p>
<p>&nbsp;</p>
<h2 id="Emulating_private_methods_with_closures">Emulando los métodos privados con closures</h2>
<p>Lenguajes como Java ofrecen la posibilidad de declarar métodos privados,&nbsp;lo que significa que sólo puede ser llamados por otros métodos en la misma clase.</p>
<p>JavaScript no proporciona una forma nativa de hacer esto,&nbsp;pero es posible emular&nbsp;métodos privados utilizando&nbsp;closures. Los métodos privados no son sólo útiles para restringir el acceso al código:&nbsp;también proporcionan una poderosa manera de administrar el espacio de nombres global 'namespace',&nbsp;manteniendo los métodos no esenciales de saturar la interfaz pública a su código.</p>
<p>Así es cómo definir algunas de las funciones públicas que pueden acceder a funciones privadas y variables, utilizando closures&nbsp;que también se conoce &nbsp;como el&nbsp;<a class="external" href="http://www.google.com/search?q=javascript+module+pattern" title="http://www.google.com/search?q=javascript+module+pattern">module pattern</a>:</p>
<pre class="brush: js">
var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */&nbsp;</pre>
<p>Hay mucho que hacer aquí.&nbsp;En los ejemplos anteriores cada closure ha tenido su propio entorno;&nbsp;aquí creamos un entorno único que es compartido por tres funciones: <code>Counter.increment</code>, <code>Counter.decrement</code>&nbsp;y&nbsp;<code>Counter.value</code>.</p>
<p>El entorno compartido se crea en el cuerpo de una función anónima, que es ejecutado tan pronto como se ha definido.&nbsp;El entorno contiene dos elementos privados:&nbsp;una variable llamada&nbsp;<code>privateCounter</code>&nbsp;y una función llamada&nbsp;<code>changeBy</code>.&nbsp;Ninguno de estos elementos privados puede accederse directamente desde fuera de la función anónima.&nbsp;En su lugar, se debe acceder por las tres funciones públicas que se devuelven desde el contenedor anónimo.</p>
<p>Esas tres funciones públicas son closure que comparten el mismo entorno.&nbsp;Gracias al alcance léxico de Javascript's,&nbsp;cada uno de ellos tienen acceso a la variable&nbsp;<code>privateCounter</code>&nbsp;y a la función&nbsp;<code>changeBy.</code></p>
<p>Usted notará que estamos definiendo una función anónima que crea un contador,&nbsp;y luego llamamos inmediatamente y asignamos el resultado a la variable&nbsp;<code>Counter</code>.&nbsp;Podríamos almacenar esta función a una variable independiente y utilizarlo para crear varios contadores.</p>
<pre class="brush: js">
var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */&nbsp;</pre>
<p>Observe cómo cada uno de los dos contadores mantiene su independencia del otro.&nbsp;Su entorno durante la llamada de la función&nbsp;<code>makeCounter()</code>&nbsp;es diferente cada vez. El closure variable <code>privateCounter&nbsp;</code>contiene una instancia diferente cada vez.</p>
<p>Utilizar closure de este modo proporciona una serie de beneficios que se asocian normalmente con&nbsp;encapsulación y ocultación de datos concreto de programación orientada a objetos.</p>
<h2 id="Creating_closures_in_loops.3A_A_common_mistake">Creando closures en loops: Un error común</h2>
<p>Antes de la introducción de&nbsp;<a href="/en-US/docs/JavaScript/Reference/Statements/let" title="let"><code>let</code> keyword</a>&nbsp;en JavaScript 1.7,&nbsp;un problema común con&nbsp;closures ocurre&nbsp;cuando se crearon dentro de un bucle&nbsp;'loop'.&nbsp;Considere el siguiente ejemplo:</p>
<pre class="brush: html">
&lt;p id="help"&gt;Helpful notes will appear here&lt;/p&gt;
&lt;p&gt;E-mail: &lt;input type="text" id="email" name="email"&gt;&lt;/p&gt;
&lt;p&gt;Name: &lt;input type="text" id="name" name="name"&gt;&lt;/p&gt;
&lt;p&gt;Age: &lt;input type="text" id="age" name="age"&gt;&lt;/p&gt;
</pre>
<pre class="brush: js">
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i &lt; helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp(); 
</pre>
<p>{{ JSFiddleLink("v7gjv") }}</p>
<p>El array <code>helpText</code>&nbsp;define tres consejos útiles,&nbsp;cada uno asociado con el ID&nbsp;de un campo de entrada en el documento.&nbsp;El bucle recorre estas definiciones,&nbsp;enlazar un evento onfocus a cada uno que muestra el método de ayuda asociada.</p>
<p>Si tratas de este código, verá que no funciona como se esperaba. No importa qué campo se centrar en, se mostrará el mensaje acerca de su edad.</p>
<p>La razón de esto es que las funciones asignadas a onfocus son closure; que consisten en la definición de la función y el entorno capturado desde el alcance de la función <code>setupHelp</code>.&nbsp;Se han creado tres&nbsp;closures,&nbsp;pero cada uno comparte el mismo entorno único.&nbsp;Por el momento que se ejecutan las devoluciones de llamada onfocus, el bucle ha seguido su curso y la variable de elemento (compartida por todos los tres closure) ha quedado apuntando a la última entrada en la lista del&nbsp;<code>helpText.</code></p>
<p>En este caso, una solución es utilizar más closure: en particular, con una fábrica de función como se ha descrito anteriormente:</p>
<pre class="brush: js">
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i &lt; helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp(); 
</pre>
<p>{{ JSFiddleLink("v7gjv/1") }}</p>
<p>This works as expected. Rather than the callbacks all sharing a single environment, the <code>makeHelpCallback</code> function creates a new environment for each one in which <code>help</code> refers to the corresponding string from the <code>helpText</code> array.</p>
<h2 id="Performance_considerations">Performance considerations</h2>
<p>It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task as it will negatively affect script performance both in terms of processing speed and memory consumption.</p>
<p>For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called the methods would get reassigned (that is, for every object creation).</p>
<p>Consider the following impractical but demonstrative case:</p>
<pre class="brush: js">
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}
</pre>
<p>The previous code does not take advantage of the benefits of closures and thus should instead be formulated:</p>
<pre class="brush: js">
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};
</pre>
<p>Or as follows:</p>
<pre class="brush: js">
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};
</pre>
<p>In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See <a href="/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model" title="en-US/docs/JavaScript/Guide/Details of the Object Model">Details of the Object Model</a> for more details.</p>
<p>{{ autoPreviousNext("JSGChapters") }}</p>
Revertir a esta revisión