¿Cómo funcionan los cierres de JavaScript?

2022-04-07 19:12:56

Las respuestas a esta pregunta son un esfuerzo de la comunidad. Edite las respuestas existentes para mejorar esta publicación. Actualmente no acepta nuevas respuestas o interacciones.

¿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

Source
zh
Responder


7625
  • Un cierre es un emparejamiento de:

    1. Una función y
    2. Una referencia al ámbito externo de esa función (entorno léxico)

    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 invoca foo, variable cerrando secreto:

    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ón inner, foo< /código>.

    Usos de los Cierres

    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.

    Variables de instancia privada

    se cierra sobre los detalles del automóvil.

    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())

    Programación Funcional

    cierra tanto 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

    Programación orientada a eventos

    cierra sobre la variable 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)

    Modularización

    y 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())

    Ejemplos

    Ejemplo 1

    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

    Ejemplo 2

    , 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

    Ejemplo 3

    , tenga cuidado de comprender qué variable está cerrando. Las variables declaradas mediante 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]()
    }

    Puntos finales:

    • Cada vez que se declara una función en JavaScript, se crea un cierre.
    • desde dentro de otra función es el ejemplo clásico de cierre, porque el estado dentro de la función externa está implícitamente disponible para la función interna devuelta, incluso después de que la función externa haya completado la ejecución.< /li> dentro de una función, se usa un cierre. El texto que 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 = …'). (la Constructor de funciones) dentro de una función, no se cierra sobre su entorno léxico: en su lugar, se cierra sobre el contexto global. La nueva función no puede hacer referencia a las variables locales de la función externa.
    • Un cierre en JavaScript es como mantener una referencia (NO una copia) al ámbito en el punto de declaración de la función, que a su vez mantiene una referencia a su ámbito externo, y así sucesivamente, todo el camino hacia el objeto global en la parte superior de la cadena de ámbito.
    • Se crea un cierre cuando se declara una función; este cierre se utiliza para configurar el contexto de ejecución cuando se invoca la función.
    • Se crea un nuevo conjunto de variables locales cada vez que se llama a una función.

    Enlaces