Explicación detallada del cierre de js

Cierre

Preguntas reales clásicas

  • ¿Qué es un cierre? ¿Cuáles son los escenarios de aplicación de los cierres? ¿Cómo destruir el cierre?

que es el cierre

El cierre es un punto de conocimiento muy importante en JavaScript , y también es uno de los puntos de conocimiento que es más probable que se pregunte en nuestras entrevistas front-end.

Abra " Programación avanzada de JavaScript " y " Guía definitiva de JavaScript ", y encontrará que hay diferentes explicaciones sobre los cierres. Al buscar cierres en Internet, también encontrará que hay diferentes opiniones, lo que hace que este punto de conocimiento en sí parezca ser diferente Un poco misterioso, incluso un poco de fantasía.

Entonces, ¿este punto de conocimiento es realmente tan profundo?

¡No! De hecho, es muy fácil entender los cierres en JavaScript , pero antes de eso necesitas conocer los dos puntos de conocimiento siguientes:

  • Alcance y cadena de alcance en JavaScript
  • Recolección de basura en JavaScript

Aquí revisamos brevemente estos dos puntos de conocimiento:

1. Alcance y cadena de alcance en JavaScript

  • El ámbito es un territorio independiente, por lo que las variables no se filtrarán ni quedarán expuestas, y las variables con el mismo nombre en diferentes ámbitos no entrarán en conflicto.
  • El alcance se determina cuando se define y no cambia.
  • Si el valor no se encuentra en el alcance actual, se buscará en el alcance superior hasta que se encuentre el alcance global. La cadena formada por dicho proceso de búsqueda se denomina cadena de alcance.

2. Recolección de basura en JavaScript

  • El entorno de ejecución de Javascript es responsable de administrar la memoria utilizada durante la ejecución del código, lo que implica un mecanismo de recolección de basura.
  • El recolector de basura encontrará periódicamente (periódicamente) aquellas variables que ya no se usan. Mientras la variable ya no se use, el recolector de basura la reciclará y luego liberará su memoria. Si la variable todavía está en uso, no se reciclará.

Bien , con estos dos puntos de conocimiento en mente, veamos qué es el cierre.

El cierre no es una técnica específica, sino un fenómeno que significa que cuando se define una función, la información del entorno circundante se puede utilizar en la función. En otras palabras, al ejecutar una función, siempre que se utilizan datos externos en la función, se crea un cierre.

La cadena de alcance es exactamente el medio para implementar el cierre.

¿Qué? Siempre que se utilicen datos externos en una función, ¿se crea un cierre?

¿En realidad? A continuación podemos demostrarlo:

imagen-20211227145016552

En el código anterior, definimos una variable i en la función a y luego imprimimos la variable i . Para la función a , la variable i existe en su propio alcance de función , por lo que podemos ver que la variable i existe en Local durante la depuración .

Modifiquemos ligeramente el código anterior, como se muestra a continuación:

imagen-20211227145521272

En el código anterior, colocamos la acción de declarar la variable i fuera de la función a . Esto significa que la función a ya no puede encontrar la variable i en su propio alcance . ¿Qué hará?

Si ha estudiado la cadena de alcance, debe saber que observará capa por capa a lo largo de la cadena de alcance. Sin embargo, como se mencionó anteriormente al introducir cierres, si esto sucede, es decir, cuando la función usa datos externos, se creará un cierre.

Observando atentamente el área de depuración, encontraremos que en este momento i está colocado en Cierre , lo que confirma nuestra afirmación anterior.

Como puede ver, los cierres en realidad no son tan difíciles de entender. Cuando sienta que una palabra le resulta particularmente difícil, también puede utilizar el método de división de palabras. Este también es un método probado que recomiendo.

"Cerrado" puede entenderse como "circuito cerrado, cerrado", y "paquete" puede entenderse como "un espacio similar a un paquete". Por lo tanto, el cierre en realidad puede considerarse como un espacio cerrado. Entonces, ¿para qué se utiliza este espacio? ? De hecho, se utiliza para almacenar variables.

imagen-20211227163947135

Entonces, ¿todas las declaraciones de variables bajo una función se colocarán en el espacio cerrado del cierre?

Realmente no, ponerlo en el cierre depende de si se hace referencia a la variable en otro lugar, por ejemplo:

imagen-20211227164333723

En el código anterior, no se crean variables en la función c, pero se imprimen i, j, k y x . Estas variables existen en las funciones a, by el alcance global respectivamente. Por lo tanto, se crean tres cierres y el alcance global El valor de i se almacena en el cierre , los valores de las variables j y k se almacenan en el cierre a y el valor de la variable x se almacena en el cierre b .

Pero si observa con atención, encontrará que la variable y en la función b no se coloca en el cierre, por lo que colocarla en el cierre depende de si se hace referencia a la variable.

Por supuesto, es posible que tenga un nuevo problema en este momento: con tantos cierres, ¿no ocupan espacio en la memoria?

De hecho, si el cierre se forma automáticamente, será destruido. Por ejemplo:

imagen-20211227174043786

En el código anterior, intentamos imprimir la variable k en la línea 16. Obviamente, se informará un error en este momento. Al establecer un punto de interrupción en la línea 16 para la depuración , podemos ver claramente que no hay cierre en este momento. El recolector de basura reciclará automáticamente las variables sin referencia sin ningún uso de memoria.

Por supuesto, a lo que me refiero aquí es a la generación automática de cierres, en cuanto a cierres, a veces necesitamos crear manualmente un cierre en función de los requisitos.

Considere el siguiente ejemplo:

function eat(){
    
    
    var food = "鸡翅";
    console.log(food);
}
eat(); // 鸡翅
console.log(food); // 报错

En el ejemplo anterior, declaramos una función llamada comer y la llamamos.

El motor JavaScript creará un contexto de ejecución para la función comer , en el que se declara la variable comida y se le asigna un valor.

Cuando se ejecuta este método, el contexto se destruye y la variable de comida desaparece. Esto se debe a que la variable de comida es una variable local de la función de comer , actúa en la función de comer y se creará y destruirá a medida que se cree el contexto de ejecución de comer . Entonces, cuando volvamos a imprimir la variable de comida, se informará un error diciéndonos que la variable no existe.

Pero modifiquemos ligeramente este código:

function eat(){
    
    
    var food = '鸡翅';
    return function(){
    
    
        console.log(food);
    }
}
var look = eat();
look(); // 鸡翅
look(); // 鸡翅

En este ejemplo, la función comer devuelve una función y se accede a la variable local comida en esta función interna . Llame a la función comer y asigne el resultado a la variable de apariencia. Esta mirada apunta a la función interna en la función comer , luego la llama y finalmente genera el valor de la comida .

La razón por la que se puede acceder a los alimentos es muy simple: como dijimos anteriormente, el recolector de basura solo reciclará variables a las que no se hace referencia, pero una vez que todavía se hace referencia a una variable, el recolector de basura no reciclará esta variable. En el ejemplo anterior, la comida debe destruirse después de llamar a eat , pero devolvimos la función anónima dentro de eat al exterior , y esta función anónima hace referencia a la comida , por lo que el recolector de basura no la reciclará. Sí, es por eso que cuando esta función anónima se llama afuera, el valor de la variable de comida aún se puede imprimir.

Llegados a este punto se ha revelado una de las ventajas o características de los cierres, es decir:

  • Los cierres permiten que el entorno externo acceda a variables locales dentro de una función.
  • Los cierres permiten que las variables locales persistan y no se destruyan junto con su contexto.

A través de esta característica, podemos resolver el problema de la contaminación variable global. En los primeros días , cuando JavaScript no se podía modularizar, cuando varias personas colaboraban, definir demasiadas variables globales podía causar conflictos de nombres de variables globales. Los cierres se usaban para resolver llamadas de funciones a variables y escribir variables en un espacio independiente. En el interior, puede resolver hasta cierto punto el problema de la contaminación variable global.

Por ejemplo:

var name = "GlobalName";
// 全局变量
var init = (function () {
    
    
    var name = "initName";
    function callName() {
    
    
        console.log(name);
        // 打印 name
    }
    return function () {
    
    
        callName();
        // 形成接口
    }
}());
init(); // initName
var initSuper = (function () {
    
    
    var name = "initSuperName";
    function callName() {
    
    
        console.log(name);
        // 打印 name
    }
    return function () {
    
    
        callName();
        // 形成接口
    }
}());
initSuper(); // initSuperName

Bien, al final de esta sección, hagamos un pequeño resumen de cierres:

  • Un cierre es un espacio cerrado que almacena valores del alcance a los que se hace referencia en otros lugares. En JavaScript , los cierres se implementan a través de cadenas de alcance.

  • Siempre que se utilicen datos externos en la función, se crea un cierre. En este caso, no necesitamos preocuparnos por el cierre creado al codificar.

  • También podemos crear cierres manualmente a través de algún medio, para que el entorno externo pueda acceder a las variables locales dentro de la función, de modo que las variables locales puedan guardarse continuamente y no destruirse junto con su contexto.

Problema clásico de cierre

Después de hablar de cierres, veamos un problema clásico de cierres.

for (var i = 1; i <= 3; i++) {
    
    
    setTimeout(function () {
    
    
        console.log(i);
    }, 1000);
}

En el código anterior, nuestro resultado esperado es generar los valores de la variable i como 1, 2 y 3 respectivamente después de 1 segundo . Sin embargo, el resultado de la ejecución es: 4, 4, 4 .

De hecho, el problema radica en los cierres. Verá, setTimeout en el bucle accede a su variable externa i , formando un cierre.

Sólo hay una variable i , por lo que se accede a la misma variable en setTimeout que se repite tres veces . Cuando el ciclo llega a la cuarta vez , la variable i aumenta a 4 , no se cumple la condición del ciclo, el ciclo finaliza y el contexto finaliza después de ejecutar el código. Sin embargo, los tres setTimeouts esperan 1 segundo antes de ejecutarse. Debido al cierre, aún pueden acceder a la variable i , pero el valor de la variable i ya es 4 en este momento .

Para resolver este problema, podemos permitir que la función anónima en setTimeout ya no acceda a variables externas, sino a sus propias variables internas, de la siguiente manera:

for (var i = 1; i <= 3; i++) {
    
    
    (function (index) {
    
    
        setTimeout(function () {
    
    
            console.log(index);
        }, 1000);
    })(i)
}

De esta manera, no es necesario acceder a la variable que declaré en el bucle for en setTimeout . En su lugar, paso el valor de la variable i a setTimeout llamando a una función para pasar parámetros , de modo que ya no creen un cierre, porque la variable i se puede encontrar en mi propio alcance .

Por supuesto, existe una forma más sencilla de resolver este problema: utilizar la palabra clave let en ES6 .

La variable que declara tiene alcance de bloque. Si la pones en un bucle, habrá una nueva variable i cada vez que se repite , por lo que incluso si hay un cierre, no habrá problema, porque cada cierre guarda una i diferente. variable, entonces el problema de ahora estará resuelto.

for (let i = 1; i <= 3; i++) {
    
    
    setTimeout(function () {
    
    
        console.log(i);
    }, 1000);
}

Respuestas a preguntas reales

  • ¿Qué es un cierre? ¿Cuáles son los escenarios de aplicación de los cierres? ¿Cómo destruir el cierre?

Un cierre es un espacio cerrado que almacena valores del alcance a los que se hace referencia en otros lugares. En JavaScript , los cierres se implementan a través de cadenas de alcance.

Siempre que se utilicen datos externos en la función, se crea un cierre. En este caso, no necesitamos preocuparnos por el cierre creado al codificar.

También podemos crear cierres manualmente a través de algún medio, para que el entorno externo pueda acceder a las variables locales dentro de la función, de modo que las variables locales puedan guardarse continuamente y no destruirse junto con su contexto.

El uso de cierres puede resolver el problema de la contaminación variable global.

Si es un cierre generado automáticamente, no necesitamos preocuparnos por la destrucción del cierre. Si es un cierre creado manualmente, podemos establecer la variable referenciada en nula, es decir, borrar manualmente la variable, para que la siguiente momento en que el recolector de basura de JavaScript realiza la recolección de basura Durante el reciclaje, si se descubre que esta variable ya no tiene ninguna referencia, se reciclará la cantidad establecida en nulo .


- EOF -

Supongo que te gusta

Origin blog.csdn.net/qq_53461589/article/details/132740029
Recomendado
Clasificación