[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/es/docs/Web/JavaScript/Guide/Closures [Back]  [Original]

Closures - JavaScript | MDN

Esta pgina ha sido traducida del ingls por la comunidad. Aprende ms y nete a la comunidad de MDN Web Docs.

View in English Always switch to English

Closures

Un closure es la combinacin de una funcin agrupada (dentro de otra) con referencias a su estado adyacente (el entorno lxico). En otras palabras, un closure te da acceso al alcance de una funcin externa desde una funcin interna. En JavaScript, los closure se crean cada vez que se crea una funcin, en el momento de la creacin de la funcin.

In this article

mbito lxico

Considere el siguiente ejemplo:

js
function init() {
  var name = "Mozilla"; // name es una variable local creada por init
  function displayName() {
    // displayName() es la funcin interna que forma el closure
    console.log(name); // usar la variable declarada en la funcin padre
  }
  displayName();
}
init();

init() crea una variable local llamada name y una funcin llamada displayName(). La funcin displayName() es una funcin interna que se define dentro de init() y est disponible solo dentro del cuerpo de la funcin init(). Tenga en cuenta que la funcin displayName() no tiene variables locales propias. Sin embargo, dado que las funciones internas tienen acceso a las variables de las funciones externas, displayName() puede acceder a la variable name declarada en la funcin principal, init().

Ejecute el cdigo utilizando este enlace de JSFiddle y observe que la instruccin console.log() dentro de la funcin displayName() muestra con xito el valor de la variable name, que se declara en su funcin principal. Este es un ejemplo de mbito lxico, que describe cmo un analizador resuelve los nombres de variables cuando las funciones estn anidadas. La palabra lxico se refiere al hecho de que el mbito lxico utiliza la ubicacin donde se declara una variable dentro del cdigo fuente para determinar dnde est disponible esa variable. Las funciones anidadas tienen acceso a variables declaradas en su mbito externo.

En este ejemplo particular, el mbito se denomina mbito de funcin o alcance de la funcin, porque la variable es accesible y solo es accesible dentro del cuerpo de la funcin donde se declara.

Alcance con let y const

Tradicionalmente (antes de ES6), JavaScript solo tena dos tipos de mbitos: mbito de la funcin y mbito global. Las variables declaradas con var tienen un alcance de funcin o un alcance global, dependiendo de si se declaran dentro de una funcin o fuera de una funcin. Esto puede ser complicado, porque los bloques con llaves rizadas no crean mbitos:

js
if (Math.random() > 0.5) {
  var x = 1;
} else {
  var x = 2;
}
console.log(x);

Para personas de otros lenguajes (por ejemplo, C, Java) donde los bloques crean mbitos, el cdigo anterior debera arrojar un error en la lnea console.log, porque estamos fuera del alcance de x en cualquiera de los bloques. Sin embargo, debido a que los bloques no crean mbitos para var, las instrucciones var aqu en realidad crean una variable global. Tambin se presenta a continuacin un ejemplo prctico que ilustra cmo esto puede causar errores reales cuando se combina con closures.

En ES6, JavaScript introdujo las declaraciones let y const, que, entre otras cosas, como zonas muertas temporales, le permiten crear variables de alcance de bloque.

js
if (Math.random() > 0.5) {
  const x = 1;
} else {
  const x = 2;
}
console.log(x); //ReferenceError: x no est definido

En esencia, los bloques finalmente se tratan como mbitos en ES6, pero solo si declaras variables con let o const. Adems, ES6 introdujo mdulos, que introdujo otro tipo de alcance. Los closure son capaces de capturar variables en todos estos mbitos, que introduciremos ms adelante.

Closure

Considere el siguiente ejemplo:

js
function makeFunc() {
  const name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  return displayName;
}

const myFunc = makeFunc();
myFunc();

Ejecutar este cdigo tiene exactamente el mismo efecto que el ejemplo anterior de la funcin init() anterior. Lo que es diferente (e interesante) es que la funcin interna displayName() se devuelve desde la funcin externa antes de ejecutarse.

A primera vista, puede parecer poco intuitivo que este cdigo siga funcionando. En algunos lenguajes de programacin, las variables locales dentro de una funcin existen solo durante la ejecucin de esa funcin. Una vez que makeFunc() termine de ejecutarse, es de esperar que la variable name ya no sea accesible. Sin embargo, debido a que el cdigo sigue funcionando como se esperaba, este obviamente no es el caso en JavaScript.

La razn es que las funciones en JavaScript forman closures. Un closure es la combinacin de una funcin y el entorno lxico dentro del cual se declar esa funcin. Este entorno consiste en cualquier variable local que estuviera dentro del alcance en el momento en que se cre el closure. En este caso, myFunc es una referencia a la instancia de la funcin displayName que se crea cuando se ejecuta makeFunc. La instancia de displayName mantiene una referencia a su entorno lxico, dentro del cual existe la variable name. Por esta razn, cuando se invoca myFunc, la variable name permanece disponible para su uso, y 'Mozilla' se pasa a console.log.

Aqu hay un ejemplo un poco ms interesante: una funcin makeAdder:

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

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

console.log(add5(2)); // 7
console.log(add10(2)); // 12

En este ejemplo, hemos definido una funcin makeAdder(x), que toma un solo argumento x y devuelve una nueva funcin. La funcin que devuelve toma un solo argumento y y devuelve la suma de la variable x y la variable y.

En esencia, makeAdder es una fbrica de funciones. Crea funciones que pueden aadir un valor especfico a su argumento. En el ejemplo anterior, la fbrica de funciones crea dos nuevas funciones: una que suma cinco a su argumento y otra que suma 10.

add5 y add10 forman closures. Comparten la misma definicin de cuerpo de funcin, pero almacenan diferentes entornos lxicos. En el entorno lxico de add5, x es 5, mientras que en el entorno lxico de add10, x es 10.

Closure prcticos

Los closure son tiles porque te permiten asociar datos (el entorno lxico) con una funcin que opera sobre esos datos. Esto tiene paralelismos obvios con la programacin orientada a objetos, donde los objetos le permiten asociar datos (las propiedades del objeto) con uno o ms mtodos.

En consecuencia, puede usar un closure en cualquier lugar donde normalmente pueda usar un objeto con un solo mtodo.

Las situaciones en las que es posible que desee hacer esto son particularmente comunes en la web. Gran parte del cdigo escrito en JavaScript para front-end est basado en eventos. Defina algn comportamiento y luego adjntelo a un evento activado por el usuario (como un clic o una pulsacin de tecla). El cdigo se adjunta como una devolucin de llamada (una sola funcin que se ejecuta en respuesta al evento).

Por ejemplo, supongamos que queremos aadir botones a una pgina para ajustar el tamao del texto. Una forma de hacerlo es especificar el tamao de fuente del elemento body (en pxeles) y luego establecer el tamao de los otros elementos de la pgina (como los encabezados) utilizando la unidad em relativa:

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

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Dichos botones interactivos de tamao de texto pueden cambiar la propiedad font-size del elemento body, y los ajustes son recogidos por otros elementos de la pgina gracias a las unidades relativas.

Aqu est el cdigo JavaScript:

js
function makeSizer(size) {
  return function () {
    document.body.style.fontSize = `${size}px`;
  };
}

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

size12, size14 y size16 son ahora funciones que cambian el tamao del texto del cuerpo a 12, 14 y 16 pxeles, respectivamente. Puede adjuntarlos a los botones como se muestra en el siguiente ejemplo de cdigo.

js
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
html
<button id="size-12">12</button>
<button id="size-14">14</button>
<button id="size-16">16</button>

Ejecuta el cdigo usando JSFiddle.

Emular mtodos privados con closures

Los lenguajes como Java le permiten declarar mtodos como privados, lo que significa que solo pueden ser llamados por otros mtodos de la misma clase.

JavaScript, antes de clases, no tena una forma nativa de declarar mtodos privados, pero era posible emular mtodos privados usando closures. Los mtodos privados no solo son tiles para restringir el acceso al cdigo. Tambin proporcionan una forma poderosa de gestionar su espacio de nombres global.

El siguiente cdigo ilustra cmo usar closures para definir funciones pblicas que pueden acceder a funciones y variables privadas. Tenga en cuenta que estos closures siguen el Patrn de diseo de mdulo.

js
const counter = (function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

En ejemplos anteriores, cada closure tena su propio entorno lxico. Aqu, sin embargo, hay un nico entorno lxico que es compartido por las tres funciones: counter.increment, counter.decrement y counter.value.

El entorno lxico compartido se crea en el cuerpo de una funcin annima, que se ejecuta tan pronto como se ha definido (tambin conocida como IIFE). El entorno lxico contiene dos elementos privados: una variable llamada privateCounter y una funcin llamada changeBy. No puedes acceder a ninguno de estos miembros privados desde fuera de la funcin annima. En su lugar, puede acceder a ellos utilizando las tres funciones pblicas que se devuelven desde el contenedor annimo.

Esas tres funciones pblicas forman closures que comparten un mismo entorno lxico. Gracias al alcance lxico de JavaScript, cada uno tiene acceso a la variable privateCounter y a la funcin changeBy.

js
const makeCounter = function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
};

const counter1 = makeCounter();
const counter2 = makeCounter();

console.log(counter1.value()); // 0.

counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.

counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.

Observa cmo los dos contadores mantienen su independencia el uno del otro. Cada closure hace referencia a una versin diferente de la variable privateCounter a travs de su propio closure. Cada vez que se llama a uno de los contadores, su entorno lxico cambia cambiando el valor de esta variable. Los cambios en el valor de la variable en un closure no afectan el valor en el otro closure.

Nota: El uso de closures de esta manera proporciona beneficios que normalmente se asocian con la programacin orientada a objetos. En particular, ocultacin de datos y encapsulacin.

Cadena de alcance de closure

Cada closure tiene tres alcances:

  • Alcance local (mbito propio)
  • Alcance envolvente (puede ser alcance de bloque, funcin o mdulo)
  • Alcance global

Un error comn es no darse cuenta de que en el caso de que la funcin externa sea en s misma una funcin anidada, el acceso al alcance de la funcin externa incluye el alcance circundante de la funcin externa, creando efectivamente una cadena de alcances de funcin. Para demostrarlo, considere el siguiente cdigo de ejemplo.

js
// mbito global
const e = 10;
function sum(a) {
  return function (b) {
    return function (c) {
      // mbito de funciones exteriores
      return function (d) {
        // mbito local
        return a + b + c + d + e;
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20

Tambin puede escribir sin funciones annimas:

js
// mbito global
const e = 10;
function sum(a) {
  return function sum2(b) {
    return function sum3(c) {
      // mbito de funciones exteriores
      return function sum4(d) {
        // mbito local
        return a + b + c + d + e;
      };
    };
  };
}

const sum2 = sum(1);
const sum3 = sum2(2);
const sum4 = sum3(3);
const result = sum4(4);
console.log(result); // 20

En el ejemplo anterior, hay una serie de funciones anidadas, todas las cuales tienen acceso al mbito de las funciones externas. En este contexto, podemos decir que los closures tienen acceso a todos los mbitos de funcin externos.

Los closure tambin pueden capturar variables en mbitos de bloque y mbitos de mdulo. Por ejemplo, lo siguiente crea un closure sobre la variable de mbito de bloque y:

js
function outer() {
  let getY;
  {
    const y = 6;
    getY = () => y;
  }
  console.log(typeof y); // undefined
  console.log(getY()); // 6
}

outer();

Los closure sobre los mdulos pueden ser ms interesantes.

js
// myModule.js
let x = 5;
export const getX = () => x;
export const setX = (val) => {
  x = val;
};

Aqu, el mdulo exporta un par de funciones getter-setter (para asignar y obtener), que se cierran sobre la variable de alcance del mdulo x. Incluso cuando x no es directamente accesible desde otros mdulos, se puede leer y escribir con las funciones.

js
import { getX, setX } from "./myModule.js";

console.log(getX()); // 5
setX(6);
console.log(getX()); // 6

Los closure tambin pueden cerrar sobre valores importados, que se consideran como enlazadas en vivo, porque cuando el valor original cambia, el importado cambia en consecuencia.

js
// myModule.js
export let x = 1;
export const setX = (val) => {
  x = val;
};
js
// closureCreator.js
import { x } from "./myModule.js";

export const getX = () => x; // Cerrar sobre un enlace en vivo importado
js
import { getX } from "./closureCreator.js";
import { setX } from "./myModule.js";

console.log(getX()); // 1
setX(2);
console.log(getX()); // 2

Crear closures en bucles: un error comn

Antes de la introduccin de la palabra clave let, se produca un problema comn con los closure cuando los creabas dentro de un bucle. Para demostrarlo, considere el siguiente cdigo de ejemplo.

html
<p id="help">Aqu aparecern notas tiles</p>
<p>Correo electrnico: <input type="text" id="email" name="email" /></p>
<p>Nombre: <input type="text" id="name" name="name" /></p>
<p>Edad: <input type="text" id="age" name="age" /></p>
js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Tu direccin de correo electrnico" },
    { id: "name", help: "Tu nombre completo" },
    { id: "age", help: "Tu edad (debes ser mayor de 16 aos)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    // La razn es el uso de `var` en esta lnea
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function () {
      showHelp(item.help);
    };
  }
}

setupHelp();

Intenta ejecutar el cdigo en JSFiddle.

La matriz helpText define tres consejos tiles, cada uno asociado con el ID de un campo de entrada en el documento. El bucle recorre estas definiciones, conectando un evento onfocus a cada uno que muestra el mtodo de ayuda asociado.

Si pruebas este cdigo, vers que no funciona como esperabas. No importa en qu campo se centre, se mostrar el mensaje sobre su edad.

La razn de esto es que las funciones asignadas a onfocus forman closures; consisten en la definicin de la funcin y el entorno capturado desde el alcance de la funcin setupHelp. El bucle ha creado tres closure, pero cada uno comparte el mismo entorno lxico nico, que tiene una variable con valores cambiantes (item). Esto se debe a que la variable item se declara con var y, por lo tanto, tiene un alcance de funcin debido a la elevacin. El valor de item.help' se determina cuando se ejecutan las devoluciones de llamada onfocus. Debido a que el bucle ya ha seguido su curso en ese momento, el objeto variable item(compartido por los tres _closure_) se ha dejado apuntando a la ltima entrada en la listahelpText`.

Una solucin en este caso es usar ms closure: en particular, usar una fbrica de funciones como se describi anteriormente:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

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

function setupHelp() {
  var helpText = [
    { id: "email", help: "Tu direccin de correo electrnico" },
    { id: "name", help: "Tu nombre completo" },
    { id: "age", help: "Tu edad (debes ser mayor de 16 aos)" },
  ];

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

setupHelp();

Ejecuta el cdigo usando este enlace de JSFiddle.

Esto funciona como se esperaba. En lugar de que todas las devoluciones de llamada compartan un nico entorno lxico, la funcin makeHelpCallback crea un nuevo entorno lxico para cada devolucin de llamada, en el que help se refiere a la cadena correspondiente de la matriz helpText.

Otra forma de escribir lo anterior utilizando cierres annimos es:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Tu direccin de correo electrnico" },
    { id: "name", help: "Tu nombre completo" },
    { id: "age", help: "Tu edad (debes ser mayor de 16 aos)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    (function () {
      var item = helpText[i];
      document.getElementById(item.id).onfocus = function () {
        showHelp(item.help);
      };
    })(); //Adjunto del detector de eventos inmediatos con el valor actual del elemento (conservado hasta la iteracin).
  }
}

setupHelp();

Si no desea utilizar ms closure, puede utilizar la palabra clave let o const:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  const helpText = [
    { id: "email", help: "Tu direccin de correo electrnico" },
    { id: "name", help: "Tu nombre completo" },
    { id: "age", help: "Tu edad (debes ser mayor de 16 aos)" },
  ];

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

setupHelp();

Este ejemplo usa const en lugar de var, por lo que cada closure vincula la variable de alcance de bloque, lo que significa que no se requieren closure adicionales.

Otra alternativa podra ser usar forEach() para iterar sobre la matriz helpText y adjuntar un detector a cada <input>, como se muestra:

js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Tu direccin de correo electrnico" },
    { id: "name", help: "Tu nombre completo" },
    { id: "age", help: "Tu edad (debes ser mayor de 16 aos)" },
  ];

  helpText.forEach(function (text) {
    document.getElementById(text.id).onfocus = function () {
      showHelp(text.help);
    };
  });
}

setupHelp();

Consideraciones de rendimiento

Como se mencion anteriormente, cada instancia de funcin gestiona su propio alcance y cierre. Por lo tanto, no es prudente crear funciones innecesariamente dentro de otras funciones si no se necesitan closures para una tarea en particular, ya que afectar negativamente el rendimiento del script tanto en trminos de velocidad de procesamiento como de consumo de memoria.

Por ejemplo, al crear un nuevo objeto/clase, los mtodos normalmente deben asociarse al prototipo del objeto en lugar de definirse en el constructor del objeto. La razn es que cada vez que se llama al constructor, los mtodos se reasignan (es decir, para cada creacin de objetos).

Observemos el siguiente caso:

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;
  };
}

Debido a que el cdigo anterior no aprovecha los beneficios de usar closures en esta instancia en particular, podramos reescribirlo para evitar usar closures de la siguiente manera:

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

Sin embargo, no se recomienda redefinir el prototipo. En su lugar, el siguiente ejemplo se adjunta al prototipo existente:

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;
};

En los dos ejemplos anteriores, el prototipo heredado puede ser compartido por todos los objetos y las definiciones del mtodo no necesitan ocurrir en cada creacin de objetos. Consulte La herencia y la cadena de prototipos para obtener ms informacin.


Web Proxy Viewer  |  New URL  |  Original Page