Entrevistador: hable sobre la comprensión de los cierres de JS y los escenarios de aplicación comunes (el papel de los cierres)

Comprensión de los cierres de JS y escenarios de aplicaciones comunes (el papel de los cierres)

El propósito principal del uso de cierres es diseñar métodos y variables privados.

  • La ventaja es que se puede evitar la contaminación de las variables.
  • La desventaja es que el cierre será residente en la memoria, lo que aumentará la cantidad de uso de memoria. El uso inadecuado puede causar fácilmente pérdidas de memoria.

En js, la función es el cierre y la función tendrá el concepto de alcance.

1. Alcance variable

Hay dos ámbitos de variable: variables globales y variables locales . En js, las variables globales se pueden leer dentro de la función, pero las variables locales dentro de la función no se pueden leer fuera de la función.

2. ¿Cómo leer las variables dentro de la función desde fuera?

function f1(){
        var n = 123;
        function f2(){    //f2是一个闭包
            alert(n)
        }    
        return f2;
    }
  • js chain scope : el objeto hijo buscará todas las variables del objeto padre nivel por nivel, pero no al revés.
  • f2 puede leer las variables en f1, siempre que se use f2 como valor de retorno, puede leer las variables internas de f1 fuera de f1

3. Concepto de cierre

  • Una función que puede leer las variables internas de otras funciones.
  • O simplemente entendida como una función definida dentro de una función, la función interna contiene una referencia a la variable en la función externa.

4. El propósito del cierre

  • Leer las variables dentro de la función
  • Deje que los valores de estas variables permanezcan siempre en la memoria. No se borrará automáticamente después de llamar a f1.
  • Es conveniente llamar a las variables locales del contexto. Propicio para el empaquetado de códigos.
    Razón : f1 es la función principal de f2, f2 se asigna a una variable global, f2 siempre existe en la memoria, la existencia de f2 depende de f1, por lo que f1 también existe siempre en la memoria y no será reciclada por el mecanismo de recolección de basura después la llamada ha terminado.

5. Comprensión de los cierres

Aquí están algunos ejemplos:

/**
 * [init description]
 * @return {[type]} [description]
 */
function init() {
    var name = "Chrome";    //创建局部变量name和局部函数alertName

    function alertName() { //alertName()是函数内部方法,是一个闭包
        alert(name); //使用了外部函数声明的变量,内部函数可以访问外部函数的变量
    }
    alertName();
}
init();

La posición donde se declara una variable en el código fuente sirve como su alcance, y la función anidada puede acceder a la variable declarada en su alcance externo

/**
 * [outFun description]
 * @return {[type]} [description]
 */
function outFun(){
    var name = "Chrome";
    function alertName(){
        alert(name);
    }
    return alertName;   //alertName被外部函数作为返回值返回了,返回的是一个闭包
}

var myFun = outFun();
myFun();

Un cierre tiene una función + su entorno léxico; el
entorno léxico se refiere a todas las variables accesibles cuando se crea la función.
myFun se refiere a un cierre, que se compone de alertName () y la cadena "Chrome" que existía cuando se creó el cierre.
alertName () contiene una referencia al nombre, y
myFunc tiene acceso a alertName (),
por lo que cuando se llama a myFunc, el nombre todavía está en un estado accesible.

/**
 * [add description]
 * @param {[type]} x [description]
 */
function add(x){
    return function(y){
        return x + y;
    };
}

var addFun1 = add(4);
var addFun2 = add(9);

console.log(addFun1(2)); //6
console.log(addFun2(2));  //11

add acepta un parámetro x, devuelve una función, su parámetro es y, y devuelve x + y
add es una fábrica de funciones, pasando un parámetro, puede crear una función que evalúe parámetros y otros parámetros.
Tanto addFun1 como addFun2 son cierres. Usan la misma definición de función, pero el entorno léxico es diferente: en addFun1, x es 4 y el último es 5.

6. Escenarios de solicitud de cierre

parámetro de paso setTimeout

//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
    alert(param)
},1000)


//通过闭包可以实现传参效果
function func(param){
    return function(){
        alert(param)
    }
}
var f1 = func(1);
setTimeout(f1,1000);

Llamar de vuelta

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="">
</head>
<style>
    body{
        font-size: 12px;
    }
    h1{
        font-size: 1.5rem;
    }
    h2{
        font-size: 1.2rem;
    }
</style>
<body>

    <p>哈哈哈哈哈哈</p>
    <h1>hhhhhhhhh</h1>
    <h2>qqqqqqqqq</h2>

    <a href="#" id="size-12">12</a>
    <a href="#" id="size-14">14</a>
    <a href="#" id="size-16">16</a>

<script>
    function changeSize(size){
        return function(){
            document.body.style.fontSize = size + 'px';
        };
    }

    var size12 = changeSize(12);
    var size14 = changeSize(14);
    var size16 = changeSize(16);

    document.getElementById('size-12').onclick = size12;
    document.getElementById('size-14').onclick = size14;
    document.getElementById('size-16').onclick = size16;
    //我们定义行为,然后把它关联到某个用户事件上(点击或者按键)。我们的代码通常会作为一个回调(事件触发时调用的函数)绑定到事件上
</script>
</body>
</html>

Variable de paquete

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>闭包模拟私有方法</title>
    <link rel="stylesheet" href="">
</head>
<body>
<script>
    //用闭包定义能访问私有函数和私有变量的公有函数。
    var counter = (function(){
        var privateCounter = 0; //私有变量
        function change(val){
            privateCounter += val;
        }
        return {
            increment:function(){   //三个闭包共享一个词法环境
                change(1);
            },
            decrement:function(){
                change(-1);
            },
            value:function(){
                return privateCounter;
            }
        };
    })();

    console.log(counter.value());//0
    counter.increment();
    counter.increment();//2
    //共享的环境创建在一个匿名函数体内,立即执行。
    //环境中有一个局部变量一个局部函数,通过匿名函数返回的对象的三个公共函数访问。

</script>
</body>
</html>

Vincular el evento de clic para el bucle de nodo

image.png

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="">
</head>
<body>

<p id="info">123</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>

<script>
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        var item = infoArr[i];
        document.getElementById(item.id).onfocus = function(){
            showContent(item.content)
        }
    }
}
setContent()

</script>
</body>
</html>

Originalmente, el código anterior estaba destinado a ser implementado. Al hacer clic en diferentes casillas, se muestra información diferente. Como resultado, solo se muestra el último elemento, "su edad".

Análisis :

  • Se crean tres cierres en el bucle, usan el mismo elemento de entorno léxico, elemento.contenido es la variable variable
  • Cuando se ejecuta onfocus, se determina item.content. En este momento, el ciclo ha finalizado y el elemento compartido por los tres cierres ha apuntado al último elemento de la matriz.

Resolver :

/**
 * 解决方法1     通过函数工厂,则函数为每一个回调都创建一个新的词法环境
 */
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function callBack(content){
    return function(){
        showContent(content)
    }
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        var item = infoArr[i];
        document.getElementById(item.id).onfocus = callBack(item.content)
    }
}
setContent()
/**
 * 解决方法2        绑定事件放在立即执行函数中
 */
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        (function(){
            var item = infoArr[i];
            document.getElementById(item.id).onfocus = function(){
                showContent(item.content)
            }
        })()//放立即执行函数,立即绑定,用每次的值绑定到事件上,而不是循环结束的值
    }
}
setContent()
/**
 * 解决方案3        用ES6声明,避免声明提前,作用域只在当前块内
 */
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        let item = infoArr[i];      //限制作用域只在当前块内
        document.getElementById(item.id).onfocus = function(){
            showContent(item.content)
        }
    }
}
setContent()

7. Ventajas, desventajas y soluciones

Ventajas :

  • Evite la contaminación de variables globales
  • Capaz de leer variables dentro de la función
  • Puede mantener una variable en la memoria.

Desventajas :

  • 1. Los cierres permanecerán en la memoria y aumentarán el uso de la memoria. El uso inadecuado puede causar fácilmente pérdidas de memoria . La solución es eliminar todas las variables locales no utilizadas antes de salir de la función.
  • 2. El cierre estará fuera de la función principal, cambiando el valor de la variable dentro de la función principal. Por lo tanto, si usa la función principal como un objeto, el cierre como su método público y la variable interna como su valor privado, debe tener cuidado en este momento. Siéntase libre de cambiar el valor de la variable interna de la función principal .

Los cierres no serán reciclados por el mecanismo de recolección de basura después de que finalice la llamada. Vamos a expandir el mecanismo de recolección de basura aquí.

8. Expansión · Mecanismo de recolección de basura

Cuando la memoria ya no se necesita, es necesario liberarla. La tarea más difícil aquí es encontrar "qué memoria asignada ya no se necesita" . Esto requiere un mecanismo de recolección de basura para determinarlo.

1. Recuento de referencias a la recolección de basura

Este es el algoritmo de recolección de basura más avanzado.
Este algoritmo simplifica la definición de "si el objeto ya no es necesario" ya que "el objeto se refiere a él".
Si no hay ninguna referencia al objeto (referencia cero), el mecanismo de recolección de basura reciclará el objeto.

ejemplo:

var o = {
  a: {
    b:2
  }
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集


var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1;      // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有

var oa = o2.a; // 引用“这个对象”的a属性
               // 现在,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
           // 但是它的属性a的对象还在被oa引用,所以还不能回收

oa = null; // a属性的那个对象现在也是零引用了
           // 它可以被垃圾回收了

Restricciones: Referencias circulares :

El algoritmo tiene una limitación: no puede manejar referencias circulares. En el siguiente ejemplo, se crean dos objetos y se refieren entre sí, formando un ciclo. Abandonan el alcance de la función después de ser llamados, por lo que ya no son útiles y se pueden reciclar. Sin embargo, el algoritmo de recuento de referencias tiene en cuenta que tienen al menos una referencia entre sí, por lo que no se reciclarán.

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

Ejemplos prácticos de referencias circulares :

IE 6, 7 utiliza el recuento de referencias para recolectar objetos DOM de recolección de basura. Este método a menudo causa pérdidas de memoria cuando se hace referencia circular a los objetos:

var div;
window.onload = function(){
  div = document.getElementById("myDivElement");
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join("*");
};

En el ejemplo anterior, el atributo circularReference en el elemento DOM myDivElement se refiere a myDivElement, lo que genera una referencia circular. Si el atributo no se muestra eliminado o establecido en nulo, el recolector de basura de conteo de referencias siempre tendrá al menos una referencia al elemento DOM que permanecerá en la memoria, incluso si se elimina del árbol DOM. Si este elemento DOM tiene una gran cantidad de datos (como en el atributo lotsOfData anterior), la memoria ocupada por estos datos nunca se liberará.

2. Algoritmo de marcado y barrido

Este algoritmo simplificó la definición de "si el objeto ya no es necesario" como "si el objeto está disponible".

Este algoritmo asume que se establece un objeto llamado raíz (en Javascript, la raíz es el objeto global). El recolector de basura comenzará periódicamente desde la raíz, buscará todos los objetos referenciados desde la raíz y luego encontrará los objetos a los que hacen referencia estos objetos ... Comenzando desde la raíz, el recolector de basura encontrará todos los objetos disponibles y recopilará todos los objetos no disponibles.

Este algoritmo es mejor que el anterior, porque los "objetos con cero referencias" siempre no están disponibles, pero lo contrario no es necesariamente cierto, consulte "referencias circulares".

Desde 2012, todos los navegadores modernos han utilizado el algoritmo de recolección de basura de marca y barrido. Todas las mejoras al algoritmo de recolección de basura de JavaScript se basan en la mejora del algoritmo de barrido de marcas, y no hay ninguna mejora en el algoritmo de barrido de marcas en sí y su definición simplificada de "si el objeto ya no es necesario".

Las referencias circulares ya no son un problema
En el ejemplo de recuento de referencias anterior, después de que regresa la llamada a la función, los dos objetos no se pueden obtener del objeto global. Por lo tanto, serán recolectados por el recolector de basura. El segundo ejemplo es el mismo, una vez que el div y su manejo de eventos no se puedan obtener de la raíz, serán reciclados por el recolector de basura.

Limitación: Aquellos objetos que no se pueden consultar desde el objeto raíz se borrarán.
Aunque esto es una limitación, rara vez encontramos situaciones similares en la práctica, por lo que los desarrolladores no se preocupan por el mecanismo de recolección de basura.

Referencia: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management

Enlace a este artículo: https://blog.csdn.net/qq_39903567/article/details/115010640

Supongo que te gusta

Origin blog.csdn.net/qq_39903567/article/details/115010640
Recomendado
Clasificación