Puesto que el OP asume que sabemos lo que es una función y variables, construyamos cosas con esos dos elementos:
Imaginemos que tenemos un programa en el que se ha declarado una función
function miFuncion (parametros) { ... }
Para simplificar mucho este escenario asumamos que esta función es una caja negra: no afecta a nada externo y se ve afectada por nada externo a su código y sus parámetros. Es la típica función predecible que siempre nos va a dar la misma salida para los mismos parámetros de entrada.
Pero ¿y si necesitamos que el resultado varíe? ¿Y si necesitamos, por ejemplo, que la función recuerde que ya ha sido llamada?
La manera más burda de solucionar este requerimiento sería crear una -horror- variable global. No vamos a entrar en por qué esta solución no es elegante ni recomendable, aceptemos que es una mala idea. Queremos algo más robusto, una variable que nadie más que esa función pueda tocar.
Aquí aparecen las clausuras de funciones. Este primer detalle es importante: el nombre completo es clausura, cierre o cerradura de función: vamos a tomar una función y encerrarla en un entorno propio que va a incluir a la función y a otras cosas. Este entorno será... otra función. Es decir, tendremos una función declarada dentro de otra. Esto significa que cada vez que llamemos a la función externa, se generará la función interna, al igual que las variables locales de una función normal:
function generarClausura () {
let variableOculta = 1;
return function funcionEncerrada(parametro) {
console.log('Me han pasado el parametro',parametro,
'y me han llamado',variableOculta,'veces');
variableOculta++;
}
}
Como puedes ver, la función interna funcionEncerrada es lo que devolvemos, por lo que su existencia no termina cuando la función externa se completa. Y, puesto que hace uso de una variable local de la función externa (en este caso variableOculta), ésta tampoco puede ser descartada. Hemos creado un entorno cerrado, solo visible para la función interna. Veámoslo en funcionamiento:
function generarClausura () {
let variableOculta = 1;
return function funcionEncerrada(parametro) {
console.log('Me han pasado el parametro',parametro,
'y me han llamado',variableOculta,'veces');
variableOculta++;
}
}
let funcionEncerrada= generarClausura();
funcionEncerrada('Primera llamada');
funcionEncerrada('Hola mundo');
Desde fuera es imposible acceder a la variableOculta porque fue declarada como variable local de la función generarClausura, pero para la funcionEncerrada se comporta como una variable global: está fuera de ella misma pero no ha desaparecido al finalizar la ejecución de ninguna de las dos funciones.
Para terminar, te pongo un ejemplo práctico pero general del uso de clausuras: Simular atributos privados en un objeto.
Primero veamos la construcción clásica para ver la limitación de Javascript: no existe el concepto de atributo privado
class MiClase {
constructor(nombre) {
this.atributo = nombre;
}
get nombre() {
return this.atributo;
}
}
let obj= new MiClase('Patito');
//usando el get
console.log(obj.nombre);
//accediendo directamente
obj.atributo = 'Pollito';
console.log(obj.nombre);
Ahora simulemos esta privacidad con clausuras:
function MiClase(nombre) {
let atributoPrivado=nombre;
this.getNombre = () => atributoPrivado;
this.setNombre = nombre => atributoPrivado=nombre;
}
let obj= new MiClase('Patito');
console.log(obj.getNombre());
obj.atributoPrivado='Pollito';
console.log(obj.getNombre());
console.log(obj);
obj.setNombre('juan');
console.log(obj.getNombre());
Incluso aunque creemos un atributo en el objeto, este será ignorado porque realmente nuestros métodos set y get trabajan con la variable encerrada, que está fuera del alcance del código que no se haya escrito dentro de la función constructor.
function algo( value ) {
var tmp = 10;
return subfunction1;
function subfunction1( ) {
tmp = 1;
}
function subfunction2( ) {
console.log( 'Se ha llamado a subfunction2( )' );
}
}
此外,我们保留不同调用之间的变量值,因为所述值在函数本身的代码之外。
通过这样做,有许多属性:
同一个函数可以有多个闭包;每个闭包都有自己的私有数据副本和自己的入口点。
Se tratan como cualquier otro objeto: su tiempo de vida está determinado por su propio contador de referencias; cuando no hay referencias a la closure, esta se elimina.
Al igual que cualquier otro objeto, no se eliminan mientras que tengan referencias ... por ejemplo, al ser usadas para callbacks.
No sé si esto pueda ser entendido por un niño de 6 años, pero puede ser al menos un comienzo para público de todas las edades :).
Dado que allí la respuesta ha sido marcada como Wiki de comunidad, respetamos la intención del autor, y la marcamos también aquí como Wiki.
Las clausuras no son mágicas
Esta página explica las clausuras para que un programador pueda entenderlas, utilizando el código JavaScript de trabajo. No es para gurús ni para programadores funcionales.
Las clausuras no son difíciles de entender una vez que el concepto de base se ha desarrollado. Sin embargo, ¡son imposibles de entender al leer cualquier artículo académico o información académica sobre ellas!
Este artículo está dirigido a programadores con cierta experiencia en programación en un lenguaje de flujo principal, y que pueden leer la siguiente función de JavaScript:
function sayHello(name) {
var text = 'Hello ' + name;
var say = function() { console.log(text); }
say();
}
sayHello('Joe');
Dos resúmenes breves
Cuando una función (foo) declara otras funciones (bar y baz), la familia de variables locales creadas en foo no se destruye cuando la función sale. Las variables simplemente se vuelven invisibles para el mundo exterior. Por lo tanto, foo puede devolver astutamente las funciones bar y baz, y éstas pueden continuar leyendo, escribiendo y comunicándose entre sí a través de esta familia cerrada de variables ("la clausura") con la que nadie más puede entrometerse, ni siquiera alguien que llame a foo otra vez en el futuro.
Una clausura es una forma de apoyar funciones de primera clase; es una expresión que puede hacer referencia a variables dentro de su alcance (cuando se declaró por primera vez), asignarse a una variable, pasarse como argumento a una función o devolverse como resultado de una función.
Un ejemplo de clausura
El siguiente código devuelve una referencia a una función:
function sayHello2(name) {
var text = 'Hello ' + name; // variable local
var say = function() { console.log(text); }
return say;
}
var say2 = sayHello2('Bob');
say2(); // imprime "Hello Bob"
La mayoría de los programadores de JavaScript entenderán cómo una referencia a una función se devuelve a una variable (say2) en el código anterior. Si no lo hace, entonces necesita comprender eso antes de que pueda aprender acerca de las clausuras. Un programador que usa C pensaría que la función devolvía un puntero a una función, y que las variables say y say2 eran cada una un puntero a una función.
Hay una diferencia crítica entre un puntero C a una función y una referencia de JavaScript a una función. En JavaScript, se puede pensar que una variable de referencia de función tiene tanto un puntero a una función como un puntero oculto a una clausura.
El código anterior tiene una clausura porque la función anónima function() { console.log(text); } se declara dentro de otra función, sayHello2 () en este ejemplo. En JavaScript, si utilizas la palabra clave functiondentro de otra función, estás creando una clausura.
En C y en la mayoría de los otros lenguajes comunes, después de que una función retorna, todas las variables locales ya no son accesibles porque se destruye el marco de pila.
En JavaScript, si declaras una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de que la función haya retornado. Esto se demuestró anteriormente, porque llamamos a la función say2 () después de haber retornado de sayHello2 (). Observa que el código que llamamos hace referencia a la variable text, que era una variable local de la función sayHello2 ().
function() { console.log(text); } // Salida de say2.toString();
Si observamos la salida de say2.toString (), podemos ver que el código se refiere a la variable text. La función anónima puede hacer referencia al texto que contiene el valor 'Hola Bob' porque las variables locales de sayHello2 () se han mantenido vivas secretamente en la clausura.
La clave es que en JavaScript una referencia de función también tiene una referencia secreta a la clausura en la que se creó, de manera similar a como los delegados son un puntero a un método más una referencia secreta a un objeto.
Más ejemplos
Por alguna razón, las clausuras parecen ser muy difíciles de entender cuando lees sobre ellas, pero cuando ves algunos ejemplos, queda claro cómo funcionan (me tomó un tiempo). Recomiendo trabajar con los ejemplos cuidadosamente hasta que entiendas cómo funcionan. Si comienzas a usar clausuras sin entender completamente cómo funcionan, ¡pronto crearías algunos errores muy extraños!
Ejemplo 3
Este ejemplo muestra que las variables locales no se copian, se mantienen por referencia. ¡Es como si el marco de pila se mantuviera vivo en la memoria incluso después de que existe la función externa!
function say667() {
// Variable local que termina dentro de la clausura
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = say667();
sayNumber(); // escribe 43
Ejemplo 4
Las tres funciones globales tienen una referencia común al mismo cierre porque todas se declaran dentro de una sola llamada a setupSomeGlobals().
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Variable local que termina dentro de la clausura
var num = 42;
// Almacena algunas referencias a funciones como variables globales.
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobals();
gLogNumber(); // 42
oldLog() // 5
Las tres funciones tienen acceso compartido a la misma clausura: las variables locales de setupSomeGlobals() cuando se definieron las tres funciones.
Ten en cuenta que en el ejemplo anterior, si llamas a setupSomeGlobals() nuevamente, se creará un nuevo cierre (stack-frame!). Las antiguas variables gLogNumber, gIncreaseNumber, gSetNumber se sobrescriben con nuevas funciones que tienen la nueva clausura. (En JavaScript, cada vez que declaras una función dentro de otra función, las funciones internas se recrean nuevamente cada vez que se llama a la función externa).
Ejemplo 5
Este ejemplo muestra que la clausura contiene variables locales que se declararon dentro de la función externa antes de salir. Ten en cuenta que la variable alice se declara realmente después de la función anónima. La función anónima se declara primero, y cuando se llama a esa función, puedes acceder a la variable alice porque alice está en el mismo ámbito (JavaScript hace la elevación de variables). También sayAlice()() simplemente llama directamente a la referencia de función devuelta por sayAlice(), es exactamente lo mismo que se hizo anteriormente pero sin la variable temporal.
function sayAlice() {
var say = function() { console.log(alice); }
// Variable local que termina dentro de la clausura.
var alice = 'Hello Alice';
return say;
}
sayAlice()();// Escribe "Hello Alice"
Difícil: también debes tener en cuenta que la variable say también está dentro de la clausura, y puede accederse a ella mediante cualquier otra función que pueda declararse dentro de sayAlice(), o puede accederse recursivamente dentro de la función que está dentro.
Ejemplo 6
Este es un verdadero problema para muchas personas, por lo que necesitas entenderlo. Ten mucho cuidado si estás definiendo una función dentro de un bucle: es posible que las variables locales de la clausura no actúen como podrías pensar.
Necesitas entender la característica de "elevación de variables" en Javascript para entender este ejemplo.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {console.log(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Usando j solo para ayudar a prevenir la confusión -- se podría usar i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() //escribe "item2 undefined" 3 veces
La línea result.push (function () {console.log (item + '' + list [i])} agrega una referencia a una función anónima tres veces en la matriz de resultados. Si no estás tan familiarizado con las funciones anónimas, piense que es como:
¡Ten en cuenta que cuando ejecutas el ejemplo, "item2 undefined" se registra tres veces! Esto se debe a que, al igual que en los ejemplos anteriores, solo hay una clausura para las variables locales para buildList (que son resultado, i y item). Cuando las funciones anónimas se llaman en la línea fnlist [j](); todos utilizan la misma clausura única, y usan el valor actual para i y para item dentro de esa clausura (donde i tiene un valor de 3 porque el bucle se había completado, y item tiene un valor de 'item2'). Ten en cuenta que estamos indexando desde 0, por lo tanto, item tiene un valor de item2. Y el i++ incrementará i al valor 3.
Puede ser útil ver qué sucede cuando se utiliza una declaración a nivel de bloque de la variable item (a través de la palabra clave let) en lugar de una declaración de variable con ámbito de función a través de la palabra clave var. Si se realiza ese cambio, entonces cada función anónima en el resultado de la matriz tiene su propia clausura. Cuando se ejecuta el ejemplo, la salida es la siguiente:
item0 undefined
item1 undefined
item2 undefined
Si la variable i también se define utilizando let en lugar de var, entonces la salida es:
item0 1
item1 2
item2 3
Ejemplo 7
En este ejemplo final, cada llamada a la función principal crea una clausura separada.
function newClosure(someNum, someRef) {
// Variables locales que terminan dentro de la clausura.
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
console.log('num: ' + num +
'; anArray: ' + anArray.toString() +
'; ref.someVar: ' + ref.someVar + ';');
}
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
Sumario
Si todo parece completamente incierto, entonces lo mejor es jugar con los ejemplos. Leer una explicación es mucho más difícil de entender que los ejemplos. Mis explicaciones de clausuras y apilamientos, etc. no son técnicamente correctas, son simplificaciones generales que pretenden ayudar a comprender. Una vez que la idea básica ha sido asimilada, puedes recoger los detalles más adelante.
Puntos finales:
Cuando se utiliza function dentro de otra función, se utiliza una clausura.
Siempre que uses eval() dentro de una función, se usa una clausura. El texto que se evalúa puede hacer referencia a las variables locales de la función, y dentro de eval incluso puedes crear nuevas variables locales utilizando eval ('var foo = ...')
Cuando utilizas new function (...) (el constructor de funciones) dentro de una función, no creas una clausura. (La nueva función no puede hacer referencia a las variables locales de la función externa).
Una clausura en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía (exit).
Probablemente es mejor pensar que en una clausura siempre se crea solo una entrada a una función, y las variables locales se agregan a esa clausura.
Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con una clausura (dado que la función contiene una declaración de función dentro de ella, y se devuelve una referencia a esa función interna o se mantiene una referencia externa de alguna manera).
Dos funciones pueden parecer que tienen el mismo texto de origen, pero tienen un comportamiento completamente diferente debido a su clausura "oculta". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene una clausura o no.
Si estás intentando realizar modificaciones dinámicas al código fuente (por ejemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), no funcionará si myFunction es una clausura ( por supuesto, nunca se le ocurriría hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...).
Es posible obtener declaraciones de funciones dentro de las declaraciones de funciones dentro de las funciones y &mdash, y puedes obtener clausuras en más de un nivel.
Creo que normalmente una clausura es un término para la función junto con las variables que se capturan. ¡Ten en cuenta que no uso esa definición en este artículo!
Sospecho que las clausuras en JavaScript difieren de las que se encuentran normalmente en los lenguajes funcionales.
Las clausuras no son más que una función que vive dentro de otra función.
Es decir, imaginemos una función casa, al llamar a esta nos devolverá el número de la casa, pero dentro de la casa podemos tener personas y al llamar a esta casa por x persona nos responderá siempre alguien, pero aquí es la magia, no es posible llamar a una persona sin antes ir a su casa!
¿Cómo querer llamar a alguien si no se tiene su número telefónico o móvil? Pero podemos ir a la casa y luego llamar a la persona! De lo contrario, conseguir su número y luego llamar a esta!
function Casa() {
// nos dice el numero de la casa
console.log("hola llegaste a la casa #21");
// ahora dentro de la casa una funcion Persona
let Persona = function() {
console.log(saludo);
}
// definimos el nombre e la persona
let saludo = 'hola soy Bryro';
// llamamos a la funcion Persona() y la almacenamos en la variable p;
let p = Persona;
// retornamos p
return p;
}
//llamamos a la casa "traduccion leemos el letrero!"
Casa();
// ahora llamamos a la persona
Casa()();
Ahora bien, vamos a expandir el ejemplo preguntando por una persona:
function Casa() {
// nos dice el numero de la casa
console.log("hola llegaste a la casa #21");
// ahora dentro de la casa una funcion Persona
let Persona = function(nombre) {
if(nombre =="Bryro"){
console.log(`${saludo} ${nombre} en que puedo ayudarte?`);
}else{
console.log(`ups! ${nombre} no vive aqui!`);
}
}
// definimos el nombre e la persona
let saludo = 'hola soy';
// llamamos a la funcion Persona() y la almacenamos en la variable p;
let p = Persona;
// retornamos p
return p;
}
//llamamos a la casa "traduccion leemos el letrero!"
Casa();
// ahora preguntamos por si vive Juan aqui!
Casa()("Juan");
// ahora preguntamos por Bryro
Casa()("Bryro");
A veces explicar conceptos de informática se hace más asequible sí recurrimos al mundo físico.
El concepto de máquina en informática no es nuevo -Máquina de Turing- pero a veces pasamos por alto este importante detalle.
Decir que toda función es en realidad una máquina, es útil, porque una máquina para prestar sus servicios puede requerir de otras máquinas o también una máquina pueden crear otras máquinas.
Veamos una máquina sencilla y muy familiar: la calculadora
El diagrama de arriba puede ser expresado en javascript de la siguiente forma
function potencia(y){
var exponente = y;
// devuelve una funcion que requiere de una base
// para que se pueda calcular la potencia
return function (base){
return Math.pow(base,exponente);
};
};
var cubo = potencia(3);
var cuadrado = potencia(2);
var raiz = potencia(1/2);
console.log(cubo(4)); // se imprime 64
console.log(cuadrado(9)); // se imprime 81
console.log(raiz(25)); // se imprime 5
// Generalizando la máquina Potencia base(ba) 'x' al exponente(exp) 'y'
function pow(ba,exp){
return potencia(exp)(ba); // el prime parentesis (exp) es para la
// primera function potencia
// el segundo parentesis (ba) corresponde
// a la funcion devuelta por la funcion
// potencia
}
console.log(pow(2,4)); // se imprime 16
Finalmente quiero agregar que la base de estos conceptos provienen del algebra: f(x) = 2x, g(w, z) = wz+4, g(f(x), z) = 2xz + 4 pero a veces esto es explicado como una mera curiosidad del saber, desconectado de la realidad. Creo que este tipo de preguntas son importantes porque nos hacen revisar las bases sobre la cual se construyen las cosas.
Créditos de íconos: robot, teclado, portátil y ordenador:
解释闭包概念的复杂性在于它需要理解其他基本概念,例如词法作用域、自由变量和函数评估环境。理解评估函数(函数定义本身)和评估函数调用(执行函数)之间的区别也很重要。
也许最好尝试将闭包理解为一个概念,而不受任何语言的实现细节的影响。
我会尽我所能解释它,因为我理解它尽可能简单。
要理解闭包,您必须在评估函数调用的过程中像语言解释器一样思考。
考虑以下示例:
首先考虑函数的静态和声明性方面,在上面的示例中,变量
nombre
是所谓的自由变量,即不是函数参数的一部分,也不是在内部声明的变量他的身体。除了这个变量,我们的函数还使用声明的变量作为它的参数(例如:)saludo
。因此,该变量
nombre
应该在我们函数的词法范围内。也就是说,它是在我们的函数声明之前声明的saludar
(不要让JavaScript 提升欺骗你)。现在让我们把编译时间放在一边,从运行时的角度来看问题。
如果我们是语言的解释器,在评估函数的调用时,例如
saludar("Hola")
,我们将需要两件事:包含函数词法范围内所有变量的值的环境(绑定)。
函数本身的代码。
这基本上是一个闭包,是这两件事的结合。
想象一下,作为JavaScript之类的语言的解释器,在运行时评估此调用时,您可以访问如下假设的结构,表示我们调用的闭包:
在评估函数时,当解释器遇到变量
saludo
时,它会在闭包的环境中查找它,当它遇到变量时nombre
,它会在闭包的环境中查找它,最后它可以评估表达式saludo + ", " + nombre
.换句话说,环境是一个字典,包含函数本身中使用的任何名称/标识符/绑定(使用其词法范围的变量)。
所以闭包就是函数本身,加上一个环境,该环境包含它与函数词法范围内的自由变量的绑定,与函数参数的绑定,通常是对函数本身的指针或引用,以防它是递归函数(在这种情况下,它将使用一个标识符来表示自己,因此需要在环境中进行绑定,这是解释器用来解析它需要在函数体中评估为的任何名称)。
如您所见,这不需要在另一个函数中声明闭包函数。它只要求它可以访问其词法范围内的自由变量。然而,考虑到词法作用域的自然属性(例如:最内层作用域可以访问所有祖先作用域中的变量,但不是相反),通常使用闭包作为一种封装形式,如这个问题的其他答案。
闭包不难理解。
假设下面的代码,我能想到的最简单的例子。
在这里,您正在创建一个闭包。当您在另一个使用外部函数变量的函数中声明一个函数时,您将创建它。它们是变量
acumulado
和valor
函数中封闭的变量quintos
。只要内部声明的函数存在,这种“闭包”就会保持这种状态。要删除闭包,只需删除对新函数的引用。
简而言之,即使在 执行之后
saltos(5)
,JavaScript 也会保留对在 (accumulated 和 value) 中声明的变量的引用,对saltos
内部创建的函数可见。如果您对 JavaScript 如何维护这些引用的内部结构感兴趣,那么有一些很好的答案,其中包括很多内部结构。我的意图是回答最初的问题,使用变量和函数等简单术语来解释闭包。
Puesto que el OP asume que sabemos lo que es una función y variables, construyamos cosas con esos dos elementos:
Imaginemos que tenemos un programa en el que se ha declarado una función
Para simplificar mucho este escenario asumamos que esta función es una caja negra: no afecta a nada externo y se ve afectada por nada externo a su código y sus parámetros. Es la típica función predecible que siempre nos va a dar la misma salida para los mismos parámetros de entrada.
Pero ¿y si necesitamos que el resultado varíe? ¿Y si necesitamos, por ejemplo, que la función recuerde que ya ha sido llamada?
La manera más burda de solucionar este requerimiento sería crear una -horror- variable global. No vamos a entrar en por qué esta solución no es elegante ni recomendable, aceptemos que es una mala idea. Queremos algo más robusto, una variable que nadie más que esa función pueda tocar.
Aquí aparecen las clausuras de funciones. Este primer detalle es importante: el nombre completo es clausura, cierre o cerradura de función: vamos a tomar una función y encerrarla en un entorno propio que va a incluir a la función y a otras cosas. Este entorno será... otra función. Es decir, tendremos una función declarada dentro de otra. Esto significa que cada vez que llamemos a la función externa, se generará la función interna, al igual que las variables locales de una función normal:
Como puedes ver, la función interna
funcionEncerrada
es lo que devolvemos, por lo que su existencia no termina cuando la función externa se completa. Y, puesto que hace uso de una variable local de la función externa (en este casovariableOculta
), ésta tampoco puede ser descartada. Hemos creado un entorno cerrado, solo visible para la función interna. Veámoslo en funcionamiento:Desde fuera es imposible acceder a la
variableOculta
porque fue declarada como variable local de la funcióngenerarClausura
, pero para lafuncionEncerrada
se comporta como una variable global: está fuera de ella misma pero no ha desaparecido al finalizar la ejecución de ninguna de las dos funciones.Para terminar, te pongo un ejemplo práctico pero general del uso de clausuras: Simular atributos privados en un objeto.
Primero veamos la construcción clásica para ver la limitación de Javascript: no existe el concepto de atributo privado
Ahora simulemos esta privacidad con clausuras:
Incluso aunque creemos un atributo en el objeto, este será ignorado porque realmente nuestros métodos set y get trabajan con la variable encerrada, que está fuera del alcance del código que no se haya escrito dentro de la función constructor.
让我们看看......我们从哪里开始......我认为在开始。
我们都知道什么是程序,对吧?
我们还可以进一步细化:
好的。容易,不是吗?现在,让我们关注功能:
请注意,本地数据不是函数的一部分;它们是外部的东西。好的,到目前为止它很简单。再靠近一点...
还是很简单...每次调用函数时,它都会从第一条语句开始执行,这使得世界上所有的逻辑:-)
但是在 Javascript(以及许多其他语言)中,可以在 functions 中定义函数。
代金券。现在,让我们做一点改变……
我们只需删除内部函数并用语句替换它......
return
在它前面放一个,以确保我们永远不会到达那里。请注意,实际上,什么都没有改变;我们不能从外部访问内部函数,并且,从函数内部,它也不会干扰return
(前面有一个权利)。代金券。现在......如果我们能以某种方式从该函数中获取地址
instrucción1-1
呢?此外,如果我们能够直接输入该地址怎么办?我们可以使用 aswitch( )
来做这件事,改变的很少:现在我们可以做
这将执行
但我们也可以
这将执行
通过我们对函数概念的这种简单改变,我们已经成功地不局限于总是从头开始执行:我们可以选择我们想要执行的部分(哪个
function
内部) 。好吧,我们已经有了闭包的第一部分:一个函数的入口点,它不对应于它的正常入口点。
那么
variable1
和variable2
呢?好吧,这就是事情变得有点复杂的地方:-)
让我们修改一下函数的调用方式:
相反,让我们想象一下,实际上,另一件事已经完成:
我们这样称呼它:
那是一个关闭:
此时,可以在 3 点中的任意一个开始执行
begin
:subfunction1
和subfunction2
。这相当于:此外,我们保留不同调用之间的变量值,因为所述值在函数本身的代码之外。
通过这样做,有许多属性:
同一个函数可以有多个闭包;每个闭包都有自己的私有数据副本和自己的入口点。
Se tratan como cualquier otro objeto: su tiempo de vida está determinado por su propio contador de referencias; cuando no hay referencias a la closure, esta se elimina.
Al igual que cualquier otro objeto, no se eliminan mientras que tengan referencias ... por ejemplo, al ser usadas para callbacks.
为了理解闭包,我假设你理解使它们成为可能的语言的基本特性:
当一个函数返回另一个函数时,第二个函数保留对第一个函数中定义的变量的引用。
闭包是维护引用、包含它们或封闭它们的能力。
惊喜!!通过从全局范围调用函数B , B可以访问A中无法从全局范围访问的变量。
Aprovecho este espacio para ofrecer una versión traducida a nuestro idioma de una de las respuestas que refieres en tu pregunta, la cual tiene como base el excelente artículo de Morris sobre Closures titulado JavaScript Closures for Dummies.
No sé si esto pueda ser entendido por un niño de 6 años, pero puede ser al menos un comienzo para público de todas las edades :).
Dado que allí la respuesta ha sido marcada como Wiki de comunidad, respetamos la intención del autor, y la marcamos también aquí como Wiki.
Las clausuras no son mágicas
Esta página explica las clausuras para que un programador pueda entenderlas, utilizando el código JavaScript de trabajo. No es para gurús ni para programadores funcionales.
Las clausuras no son difíciles de entender una vez que el concepto de base se ha desarrollado. Sin embargo, ¡son imposibles de entender al leer cualquier artículo académico o información académica sobre ellas!
Este artículo está dirigido a programadores con cierta experiencia en programación en un lenguaje de flujo principal, y que pueden leer la siguiente función de JavaScript:
Dos resúmenes breves
Cuando una función (
foo
) declara otras funciones (bar
ybaz
), la familia de variables locales creadas enfoo
no se destruye cuando la función sale. Las variables simplemente se vuelven invisibles para el mundo exterior. Por lo tanto,foo
puede devolver astutamente las funcionesbar
ybaz
, y éstas pueden continuar leyendo, escribiendo y comunicándose entre sí a través de esta familia cerrada de variables ("la clausura") con la que nadie más puede entrometerse, ni siquiera alguien que llame afoo
otra vez en el futuro.Una clausura es una forma de apoyar funciones de primera clase; es una expresión que puede hacer referencia a variables dentro de su alcance (cuando se declaró por primera vez), asignarse a una variable, pasarse como argumento a una función o devolverse como resultado de una función.
Un ejemplo de clausura
El siguiente código devuelve una referencia a una función:
La mayoría de los programadores de JavaScript entenderán cómo una referencia a una función se devuelve a una variable (
say2
) en el código anterior. Si no lo hace, entonces necesita comprender eso antes de que pueda aprender acerca de las clausuras. Un programador que usa C pensaría que la función devolvía un puntero a una función, y que las variablessay
ysay2
eran cada una un puntero a una función.Hay una diferencia crítica entre un puntero C a una función y una referencia de JavaScript a una función. En JavaScript, se puede pensar que una variable de referencia de función tiene tanto un puntero a una función como un puntero oculto a una clausura.
El código anterior tiene una clausura porque la función anónima
function() { console.log(text); }
se declara dentro de otra función,sayHello2 ()
en este ejemplo. En JavaScript, si utilizas la palabra clavefunction
dentro de otra función, estás creando una clausura.En C y en la mayoría de los otros lenguajes comunes, después de que una función retorna, todas las variables locales ya no son accesibles porque se destruye el marco de pila.
En JavaScript, si declaras una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de que la función haya retornado. Esto se demuestró anteriormente, porque llamamos a la función
say2 ()
después de haber retornado desayHello2 ()
. Observa que el código que llamamos hace referencia a la variabletext
, que era una variable local de la funciónsayHello2 ()
.Si observamos la salida de
say2.toString ()
, podemos ver que el código se refiere a la variabletext
. La función anónima puede hacer referencia al texto que contiene el valor 'Hola Bob' porque las variables locales desayHello2 ()
se han mantenido vivas secretamente en la clausura.La clave es que en JavaScript una referencia de función también tiene una referencia secreta a la clausura en la que se creó, de manera similar a como los delegados son un puntero a un método más una referencia secreta a un objeto.
Más ejemplos
Por alguna razón, las clausuras parecen ser muy difíciles de entender cuando lees sobre ellas, pero cuando ves algunos ejemplos, queda claro cómo funcionan (me tomó un tiempo). Recomiendo trabajar con los ejemplos cuidadosamente hasta que entiendas cómo funcionan. Si comienzas a usar clausuras sin entender completamente cómo funcionan, ¡pronto crearías algunos errores muy extraños!
Ejemplo 3
Este ejemplo muestra que las variables locales no se copian, se mantienen por referencia. ¡Es como si el marco de pila se mantuviera vivo en la memoria incluso después de que existe la función externa!
Ejemplo 4
Las tres funciones globales tienen una referencia común al mismo cierre porque todas se declaran dentro de una sola llamada a
setupSomeGlobals()
.Las tres funciones tienen acceso compartido a la misma clausura: las variables locales de
setupSomeGlobals()
cuando se definieron las tres funciones.Ten en cuenta que en el ejemplo anterior, si llamas a
setupSomeGlobals()
nuevamente, se creará un nuevo cierre (stack-frame!). Las antiguas variablesgLogNumber
,gIncreaseNumber
,gSetNumber
se sobrescriben con nuevas funciones que tienen la nueva clausura. (En JavaScript, cada vez que declaras una función dentro de otra función, las funciones internas se recrean nuevamente cada vez que se llama a la función externa).Ejemplo 5
Este ejemplo muestra que la clausura contiene variables locales que se declararon dentro de la función externa antes de salir. Ten en cuenta que la variable
alice
se declara realmente después de la función anónima. La función anónima se declara primero, y cuando se llama a esa función, puedes acceder a la variablealice
porquealice
está en el mismo ámbito (JavaScript hace la elevación de variables). TambiénsayAlice()()
simplemente llama directamente a la referencia de función devuelta porsayAlice()
, es exactamente lo mismo que se hizo anteriormente pero sin la variable temporal.Difícil: también debes tener en cuenta que la variable
say
también está dentro de la clausura, y puede accederse a ella mediante cualquier otra función que pueda declararse dentro desayAlice()
, o puede accederse recursivamente dentro de la función que está dentro.Ejemplo 6
Este es un verdadero problema para muchas personas, por lo que necesitas entenderlo. Ten mucho cuidado si estás definiendo una función dentro de un bucle: es posible que las variables locales de la clausura no actúen como podrías pensar.
Necesitas entender la característica de "elevación de variables" en Javascript para entender este ejemplo.
La línea
result.push (function () {console.log (item + '' + list [i])}
agrega una referencia a una función anónima tres veces en la matriz de resultados. Si no estás tan familiarizado con las funciones anónimas, piense que es como:¡Ten en cuenta que cuando ejecutas el ejemplo, "item2 undefined" se registra tres veces! Esto se debe a que, al igual que en los ejemplos anteriores, solo hay una clausura para las variables locales para
buildList
(que son resultado,i
yitem
). Cuando las funciones anónimas se llaman en la líneafnlist [j]()
; todos utilizan la misma clausura única, y usan el valor actual parai
y paraitem
dentro de esa clausura (dondei
tiene un valor de 3 porque el bucle se había completado, yitem
tiene un valor de 'item2'). Ten en cuenta que estamos indexando desde 0, por lo tanto,item
tiene un valor de item2. Y eli++
incrementarái
al valor 3.Puede ser útil ver qué sucede cuando se utiliza una declaración a nivel de bloque de la variable
item
(a través de la palabra clavelet
) en lugar de una declaración de variable con ámbito de función a través de la palabra clavevar
. Si se realiza ese cambio, entonces cada función anónima en el resultado de la matriz tiene su propia clausura. Cuando se ejecuta el ejemplo, la salida es la siguiente:Si la variable
i
también se define utilizandolet
en lugar devar
, entonces la salida es:Ejemplo 7
En este ejemplo final, cada llamada a la función principal crea una clausura separada.
Sumario
Si todo parece completamente incierto, entonces lo mejor es jugar con los ejemplos. Leer una explicación es mucho más difícil de entender que los ejemplos. Mis explicaciones de clausuras y apilamientos, etc. no son técnicamente correctas, son simplificaciones generales que pretenden ayudar a comprender. Una vez que la idea básica ha sido asimilada, puedes recoger los detalles más adelante.
Puntos finales:
Cuando se utiliza
function
dentro de otra función, se utiliza una clausura.Siempre que uses
eval()
dentro de una función, se usa una clausura. El texto que se evalúa puede hacer referencia a las variables locales de la función, y dentro deeval
incluso puedes crear nuevas variables locales utilizandoeval ('var foo = ...')
Cuando utilizas
new function (...)
(el constructor de funciones) dentro de una función, no creas una clausura. (La nueva función no puede hacer referencia a las variables locales de la función externa).Una clausura en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía (
exit
).Probablemente es mejor pensar que en una clausura siempre se crea solo una entrada a una función, y las variables locales se agregan a esa clausura.
Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con una clausura (dado que la función contiene una declaración de función dentro de ella, y se devuelve una referencia a esa función interna o se mantiene una referencia externa de alguna manera).
Dos funciones pueden parecer que tienen el mismo texto de origen, pero tienen un comportamiento completamente diferente debido a su clausura "oculta". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene una clausura o no.
Si estás intentando realizar modificaciones dinámicas al código fuente (por ejemplo:
myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));)
, no funcionará simyFunction
es una clausura ( por supuesto, nunca se le ocurriría hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...).Es posible obtener declaraciones de funciones dentro de las declaraciones de funciones dentro de las funciones y &mdash, y puedes obtener clausuras en más de un nivel.
Creo que normalmente una clausura es un término para la función junto con las variables que se capturan. ¡Ten en cuenta que no uso esa definición en este artículo!
Sospecho que las clausuras en JavaScript difieren de las que se encuentran normalmente en los lenguajes funcionales.
Enlaces
La forma más simple de explicarlo es:
Las clausuras no son más que una función que vive dentro de otra función.
Es decir, imaginemos una función
casa
, al llamar a esta nos devolverá el número de la casa, pero dentro de la casa podemos tenerpersonas
y al llamar a esta casa porx
persona nos responderá siempre alguien, pero aquí es la magia, no es posible llamar a una persona sin antes ir a su casa!¿Cómo querer llamar a alguien si no se tiene su número telefónico o móvil? Pero podemos ir a la casa y luego llamar a la persona! De lo contrario, conseguir su número y luego llamar a esta!
Ahora bien, vamos a expandir el ejemplo preguntando por una persona:
Creamos máquinas
Créditos de íconos: robot, teclado, portátil y ordenador:
来自www.flaticon.com的Dave Gandy 制作的图标由 CC 3.0 BY 授权;来自www.flaticon.com的SimpleIcon制作的图标由 CC 3.0 BY 授权; 来自www.flaticon.com的Eucalyp制作的图标由 CC 3.0 BY 授权