¿Cómo explicaría los cierres de JavaScript a alguien que conoce los conceptos en los que consisten (por ejemplo, funciones, variables y similares), pero que no entiende los cierres en sí mismos?
He visto el ejemplo de Scheme dado en Wikipedia, pero desafortunadamente lo hizo no ayuda.
- e-satis
SourceUn cierre es un emparejamiento de:
Un entorno léxico es parte de cada contexto de ejecución (marco de pila) y es un mapa entre identificadores (es decir, nombres de variables locales) y valores.
Cada función en JavaScript mantiene una referencia a su entorno léxico externo. Esta referencia se utiliza para configurar el contexto de ejecución que se crea cuando se invoca una función. Esta referencia permite que el código dentro de la función "vea" las variables declaradas fuera de la función, independientemente de cuándo y dónde se llame a la función.
Si una función fue llamada por una función, que a su vez fue llamada por otra función, entonces se crea una cadena de referencias a entornos léxicos externos. Esta cadena se denomina cadena de ámbito.
forma un cierre con el entorno léxico del contexto de ejecución creado cuando se invocafoo
, variable cerrando function foo() {
const secreto = Math.trunc(Math.random() * 100)
función de retorno interior() {
console.log(`El número secreto es ${secreto}.`)
}
}
const f = foo() // `secret` no es directamente accesible desde fuera de `foo`
f() // La única forma de recuperar `secret` es invocar `f`
En otras palabras: en JavaScript, las funciones llevan una referencia a una "caja de estado" privada, a la que solo ellas (y cualquier otra función declarada dentro del mismo entorno léxico) tienen acceso. Este cuadro de estado es invisible para la persona que llama a la función, lo que ofrece un mecanismo excelente para ocultar y encapsular datos.
Y recuerde: las funciones en JavaScript se pueden pasar como variables (funciones de primera clase), lo que significa que estos pares de funcionalidad y estado se pueden pasar por su programa: similar a cómo podría pasar una instancia de una clase en C++ .
Si JavaScript no tuviera cierres, entonces se tendrían que pasar más estados entre las funciones explícitamente, lo que haría que las listas de parámetros fueran más largas y el código más ruidoso.
Entonces, si desea que una función siempre tenga acceso a una parte privada del estado, puede usar un cierre.
...y con frecuencia queremos asociar el estado con una función. Por ejemplo, en Java o C++, cuando agrega una variable de instancia privada y un método a una clase, está asociando el estado con la funcionalidad.
permanece disponible para el objeto de funcióninner
, foo< /código>.
Los cierres son útiles siempre que necesite un estado privado asociado con una función. Este es un escenario muy común, y recuerde: JavaScript no tenía una sintaxis de clase hasta 2015 y todavía no tiene una sintaxis de campo privado. Los cierres satisfacen esta necesidad.
función Coche(fabricante, modelo, año, color) {
regreso {
Encadenar() {
devuelve `${fabricante} ${modelo} (${año}, ${color})`
}
}
}
const coche = coche nuevo('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver')
consola.log(coche.toString())
fn
como args
.
función curry(fn) {
argumentos constantes = []
función de retorno interior (arg) {
if(argumentos.longitud === fn.longitud) return fn(...argumentos)
argumentos.push(arg)
volver interior
}
}
función suma(a, b) {
devolver a + b
}
const curryAdd = curry(agregar)
console.log(curredAdd(2)(3)()) // 5
BACKGROUND_COLOR
.
const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200, 200, 242, 1)'
función al hacer clic () {
$('cuerpo').estilo.fondo = COLOR_FONDO
}
$('botón').addEventListener('clic', onClick)
toString
se cierran sobre el estado privado y las funciones que necesitan para completar su trabajo. Los cierres nos han permitido modularizar y encapsular nuestro código.
let namespace = {};
(función foo(n) {
sean numeros = []
formato de función (n) {
devuelve Matemáticas.trunc(n)
}
función tic () {
números.push(Math.random() * 100)
}
función a la cadena () {
devolver números.mapa(formato)
}
n.contador = {
garrapata,
Encadenar
}
}(espacio de nombres))
const contador = espacio de nombres.contador
contador.tick()
contador.tick()
consola.log(contador.toString())
Este ejemplo muestra que las variables locales no se copian en el cierre: el cierre mantiene una referencia a las variables originales mismas. Es como si el marco de pila se mantuviera vivo en la memoria incluso después de que la función externa finaliza.
function foo() {
sea x = 42
let interior = () => consola.log(x)
x = x + 1
volver interior
}
foo()() // registros 43
increment
y update
se cierran en el mismo entorno léxico.
, se crea un nuevo contexto de ejecución (marco de pila) y una variable completamente nueva x
, y un nuevo conjunto de funciones ( etc.) que cierran sobre esta nueva variable.
función createObject() {
sea x = 42;
regreso {
registro () {consola. registro (x)},
incremento() {x++},
actualizar (valor) { x = valor }
}
}
const o = crearObjeto()
o.incremento()
o.log() // 43
o.actualizar(5)
o.log() // 5
const p = crearObjeto()
p.log() // 42
var
se elevan. Esto es un problema mucho menor en JavaScript moderno debido a la introducción de let
y const
.
, que se cierra sobre i
. Pero debido a que var i
se eleva fuera del ciclo, todas estas funciones internas se cierran sobre la misma variable, lo que significa que el valor final de i
(3) se imprime tres veces .
function foo() {
var resultado = []
para (var i = 0; i < 3; i++) {
resultado.push(función interior() { consola.log(i) } )
}
resultado devuelto
}
resultado constante = foo()
// Lo siguiente imprimirá `3`, tres veces...
para (var i = 0; i < 3; i++) {
resultado[i]()
}
eval
puede hacer referencia a las variables locales de la función y, en el modo no estricto, incluso puede crear nuevas variables locales usando eval('var foo = …').