Entrevistador: fale sobre a compreensão dos encerramentos JS e cenários de aplicativos comuns (a função dos encerramentos)

Compreensão dos encerramentos JS e cenários de aplicativos comuns (a função dos encerramentos)

O principal objetivo do uso de encerramentos é projetar métodos e variáveis ​​privados.

  • A vantagem é que a poluição das variáveis ​​pode ser evitada
  • A desvantagem é que o encerramento ficará residente na memória, o que aumentará a quantidade de uso da memória. O uso impróprio pode facilmente causar vazamentos de memória.

Em js, a função é o fechamento e a função terá o conceito de escopo.

1. Escopo variável

Existem dois escopos de variáveis: variáveis ​​globais e variáveis ​​locais . Em js, as variáveis ​​globais podem ser lidas dentro da função, mas as variáveis ​​locais dentro da função não podem ser lidas fora da função.

2. Como ler as variáveis ​​dentro da função de fora?

function f1(){
        var n = 123;
        function f2(){    //f2是一个闭包
            alert(n)
        }    
        return f2;
    }
  • escopo da cadeia js : O objeto filho irá procurar todas as variáveis ​​do objeto pai nível por nível, mas não vice-versa.
  • f2 pode ler as variáveis ​​em f1, desde que f2 seja usado como o valor de retorno, você pode ler as variáveis ​​internas de f1 fora de f1

3. Conceito de fechamento

  • Uma função que pode ler as variáveis ​​internas de outras funções.
  • Ou simplesmente entendida como uma função definida dentro de uma função, a função interna contém uma referência à variável na função externa.

4. O objetivo do fechamento

  • Leia as variáveis ​​dentro da função
  • Deixe os valores dessas variáveis ​​sempre permanecerem na memória. Não será apagado automaticamente depois que f1 for chamado.
  • É conveniente chamar as variáveis ​​locais do contexto. Propício para o empacotamento de código.
    Motivo : f1 é a função pai de f2, f2 é atribuído a uma variável global, f2 sempre existe na memória, a existência de f2 depende de f1, então f1 também sempre existe na memória e não será reciclado pelo mecanismo de coleta de lixo após a chamada acabou.

5. Compreensão de fechamentos

Aqui estão alguns exemplos:

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

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

A posição onde uma variável é declarada no código-fonte serve como seu escopo, e a função aninhada pode acessar a variável declarada em seu escopo externo

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

var myFun = outFun();
myFun();

Um closure tem uma função + seu ambiente léxico; o
ambiente lexical se refere a todas as variáveis ​​acessíveis quando a função é criada.
myFun se refere a um encerramento, que é composto por alertName () e a string "Chrome" que existia quando o encerramento foi criado.
alertName () contém uma referência a name e
myFunc mantém acesso a alertName (),
portanto , quando myFunc é chamado, name ainda está em um estado acessível.

/**
 * [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 aceita um parâmetro x, retorna uma função, seu parâmetro é y, e retorna x + y
add é uma fábrica de funções, passando um parâmetro, você pode criar uma função que avalia parâmetros e outros parâmetros.
Ambos addFun1 e addFun2 são encerramentos. Eles usam a mesma definição de função, mas o ambiente léxico é diferente. Em addFun1, x é 4 e o último é 5.

6. Cenários de aplicação de fechamento

parâmetro de passagem setTimeout

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


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

Ligar de volta

<!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>

Variável de pacote

<!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>

Vinculando o evento de clique para o nó de loop

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>

O código acima foi originalmente planejado para ser implementado. Clicar em caixas diferentes exibe informações diferentes. Como resultado, apenas o último item é exibido, "sua idade".

Análise :

  • Três fechamentos são criados no loop, eles usam o mesmo item de ambiente léxico, item.content é a variável variável
  • Quando onfocus é executado, item.content é determinado.Neste momento, o loop terminou, e o item compartilhado pelos três closures apontou para o último item do array.

Resolva :

/**
 * 解决方法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. Vantagens, desvantagens e soluções

Vantagens :

  • Evite a poluição das variáveis ​​globais
  • Capaz de ler variáveis ​​dentro da função
  • Pode manter uma variável na memória

Desvantagens :

  • 1. Os fechamentos permanecerão na memória e aumentarão o uso da memória. O uso impróprio pode facilmente causar vazamentos de memória . A solução é excluir todas as variáveis ​​locais não utilizadas antes de sair da função.
  • 2. O fechamento será fora da função pai, alterando o valor da variável dentro da função pai. Portanto, se você usar a função pai como um objeto, o encerramento como seu método público e a variável interna como seu valor privado, você deve ter cuidado neste momento. Sinta-se à vontade para alterar o valor da variável interna da função pai .

Os fechamentos não serão reciclados pelo mecanismo de coleta de lixo após o término da chamada. Vamos expandir o mecanismo de coleta de lixo aqui.

8. Expansão · Mecanismo de coleta de lixo

Quando a memória não é mais necessária, ela precisa ser liberada.A tarefa mais difícil aqui é descobrir "qual memória alocada de fato não é mais necessária" . Isso requer um mecanismo de coleta de lixo para determinar.

1. Coleta de lixo de contagem de referência

Este é o algoritmo de coleta de lixo mais avançado.
Este algoritmo simplifica a definição de "se o objeto não é mais necessário", pois "o objeto se refere a ele".
Se não houver referência ao objeto (referência zero), o objeto será reciclado pelo mecanismo de coleta de lixo.

exemplo:

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属性的那个对象现在也是零引用了
           // 它可以被垃圾回收了

Restrições: Referências circulares :

O algoritmo tem uma limitação: ele não pode lidar com referências circulares. No exemplo a seguir, dois objetos são criados e se referem um ao outro, formando um ciclo. Eles deixam o escopo da função após serem chamados, portanto, não são mais úteis e podem ser reciclados. No entanto, o algoritmo de contagem de referência leva em consideração que eles têm pelo menos uma referência entre si, portanto, não serão reciclados.

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

  return "azerty";
}

f();

Exemplos práticos de referências circulares :

IE 6, 7 usa contagem de referência para objetos DOM de coleta de lixo. Este método costuma causar vazamentos de memória quando os objetos são referenciados circularmente:

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

No exemplo acima, o atributo circularReference no elemento DOM myDivElement se refere a myDivElement, causando uma referência circular. Se o atributo não for exibido removido ou definido como nulo, o coletor de lixo de contagem de referência sempre terá pelo menos uma referência ao elemento DOM que permanecerá na memória, mesmo se for excluído da árvore DOM. Se este elemento DOM tiver uma grande quantidade de dados (como no atributo lotesOfData acima), a memória ocupada por esses dados nunca será liberada.

2. Algoritmo de marcação e varredura

Este algoritmo simplificou a definição de "se o objeto não é mais necessário" como "se o objeto está disponível".

Este algoritmo assume que um objeto denominado raiz está definido (em Javascript, a raiz é o objeto global). O coletor de lixo irá iniciar periodicamente a partir da raiz, encontrará todos os objetos referenciados a partir da raiz e, em seguida, encontrará os objetos referenciados por esses objetos ... Começando pela raiz, o coletor de lixo encontrará todos os objetos disponíveis e coletará todos os objetos indisponíveis.

Este algoritmo é melhor que o anterior, pois "objetos com referências zero" estão sempre indisponíveis, mas o contrário não é necessariamente verdadeiro, referem-se a "referências circulares".

Desde 2012, todos os navegadores modernos usam o algoritmo de coleta de lixo por marcação e varredura. Todas as melhorias no algoritmo de coleta de lixo JavaScript são baseadas na melhoria do algoritmo de varredura de marca e não há nenhuma melhoria no próprio algoritmo de varredura de marca e sua definição simplificada de "se o objeto não é mais necessário".

As referências circulares não são mais um problema.
No exemplo de contagem de referência acima, após o retorno da chamada de função, os dois objetos não podem ser obtidos do objeto global. Portanto, eles serão recolhidos pelo coletor de lixo. O segundo exemplo é o mesmo, uma vez que o div e seu tratamento de eventos não podem ser obtidos da raiz, eles serão reciclados pelo coletor de lixo.

Limitação: os objetos que não podem ser consultados do objeto raiz serão apagados.
Embora seja uma limitação, raramente encontramos situações semelhantes na prática, de modo que os desenvolvedores não se importam com o mecanismo de coleta de lixo.

Referência: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management

Link para este artigo: https://blog.csdn.net/qq_39903567/article/details/115010640

Acho que você gosta

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