Uma recapitulação dos encerramentos de javascript

Prefácio: Este artigo foi postado originalmente aqui no meu blog pessoal .

Os fechamentos de Javascript sempre foram uma das coisas que usei para navegar por intuição. Recentemente, no entanto, ao descobrir algum código que não grocei muito bem, ficou claro que eu deveria tentar obter um entendimento mais formal. Este post tem como objetivo principal uma recapitulação rápida do meu futuro eu. Não entrará em todos os detalhes sobre fechamentos; em vez disso, ele se concentrará nas partes que considero mais úteis.

Parece haver poucas visões gerais passo a passo de encerramentos de javascript. Na verdade, só encontrei dois. Felizmente, ambos são joias absolutas. Você pode encontrá-los aqui e aqui . Recomendo vivamente esses dois artigos para qualquer pessoa que deseje obter uma compreensão mais completa dos fechamentos.

Noções básicas de fechamento

Vou tomar emprestado descaradamente algumas linhas do primeiro dos dois artigos vinculados acima para ilustrar o conceito básico de uma tampa.

function doSome() {
var x = 10;

function f(y) {
return x + y;
}
return f;
}

var foo = doSome();
foo
(20); // returns 30
foo
(30); // returns 40

No exemplo acima, a função f cria um fechamento. Se você olhar apenas para f, parece que a variável x não está definida. Na verdade, x é obtido da função de fechamento. Um fechamento é uma função que fecha (ou sobrevive) variáveis ​​da função envolvente. No exemplo acima, a função f cria um fechamento porque fecha a variável x em seu escopo. Se o objeto de fechamento, uma instância de Function, ainda estiver ativo, a variável fechada x se mantém viva. É assim que o escopo da variável x é estendido.

Isso é realmente tudo que você precisa saber sobre encerramentos: eles se referem a variáveis ​​declaradas fora do escopo da função e, ao fazer isso, mantêm essas variáveis ​​vivas. O comportamento de fechamento pode ser inteiramente explicado apenas mantendo essas duas coisas em mente.

Fechamentos e tipos de dados primitivos

O restante deste post examinará alguns exemplos de código para ilustrar o comportamento de fechamentos para parâmetros primitivos e de objeto. Nesta seção, daremos uma olhada no comportamento de um fechamento com um parâmetro de tipo de dados primitivo.

Exemplo 1

O código abaixo será nosso ponto de partida para estudar fechamentos. Certifique-se de dar uma boa olhada nele, pois todos os nossos exemplos serão uma variação deste código. Ao longo desta postagem, vamos tentar entender os fechamentos examinando os valores retornados pela função.foo()

var prim = 1;

var foo = function(p) {
var f = function() {
return p;
}
return f;
}(prim);

foo
(); // returns 1
prim
= 3;
foo
(); // returns 1

Quando o tempo de execução do javascript deseja resolver o valor retornado por , ele descobre que essa variável p é a mesma que a variável p de . Em outras palavras, não há ligação direta entre op de e a variável prim de . Vemos que isso é verdade porque atribuir um novo valor a prim não altera o valor retornado por .return p;var foo = function(p) {return p;var prim = 1;foo()

Exemplo 2

Agora, vamos dar uma olhada no que acontece quando fazemos uma pequena alteração no exemplo de código anterior, adicionando a linha a ele.p = 2;

var prim = 1;

var foo = function(p) {
var f = function() {
return p;
}
p
= 2;
return f;
}(prim);

foo
(); // returns 2
prim
= 3;
foo
(); // returns 2

O código acima é interessante porque mostra que a variável p de é de fato igual à variável p de . Mesmo que a variável f seja criada em um momento em que p é definido como 1, o ato de definir p como 2 realmente faz com que o valor retornado por mude. Este é um ótimo exemplo de fechamento mantendo uma variável fechada ativa.return p;var foo = function(p) {foo()

Exemplo 3

Este exemplo mostra um código semelhante ao primeiro, mas desta vez fechamos o fechamento sobre a variável prim.

var prim = 1;

var foo = function() {
return prim;
}

foo
(); // returns 1
prim
= 3;
foo
(); // returns 3

Aqui também podemos fazer uma dedução semelhante à que fizemos nas amostras anteriores. Quando o tempo de execução do javascript deseja resolver o valor retornado por , ele descobre que esta variável prim é a mesma que a variável prim de . Isso explica porque definir prim para 3 faz com que o valor retornado por mude.return prim;var prim = 1;foo()

Fechamentos e objetos

Nesta seção, veremos o que acontece quando pegamos nossas amostras de código e alteramos o parâmetro de um tipo de dados primitivo para um objeto.

Exemplo 1.a

O código abaixo é interessante porque na seção anterior vimos que um exemplo semelhante usando um parâmetro primitivo tinha ambas as chamadas para retornar o mesmo valor. Então, o que é diferente aqui? Vamos inspecionar como o tempo de execução resolve as variáveis ​​envolvidas.foo()

var obj = ["a"];

var foo = function(o) {
var f = function() {
return o.length;
}
return f;
}(obj);

foo
(); // returns 1
obj
[1] = "b"; // modifies the object pointed to by the obj var
obj
[2] = "c"; // modifies the object pointed to by the obj var
foo
(); // returns 3

Quando o tempo de execução tenta resolver a variável o from , ele descobre que essa variável o é a mesma que a variável o from . Vimos exatamente a mesma coisa na seção anterior. Ao contrário da seção anterior, a variável o agora contém uma referência a um objeto de matriz. Isso faz com que nosso fechamento tenha um link direto para este objeto de array e, portanto, quaisquer alterações nele serão refletidas na saída de . Isso explica por que a segunda chamada para fornece uma saída diferente da primeira.return o.length;var foo = function(o) {foo()foo()

Uma boa regra prática é assim:

  • se uma variável fechada contém um valor, o fechamento se vincula a essa variável
  • se uma variável fechada contém uma referência a um objeto, então o fechamento se vincula a esse objeto e pegará todas as alterações feitas nele

Exemplo 1.b

Observe que o fechamento só pegará as alterações feitas no objeto específico que estava presente quando o fechamento foi criado. Atribuir um novo objeto à variável obj após o fechamento ter sido criado não terá efeito. O código abaixo ilustra isso.

var obj = ["a"];

var foo = function(o) {
var f = function() {
return o.length;
}
return f;
}(obj);

foo
(); // returns 1
obj
= ["a", "b", "c"]; // assign a new array object to the obj variable
foo
(); // returns 1

Na verdade, esse código é praticamente idêntico ao código do Exemplo 1 da seção anterior.

Exemplo 2

Agora vamos modificar um pouco o exemplo de código anterior. Desta vez, veremos o que acontece quando adicionamos a linha .o[1] = "b";

var obj = ["a"];

var foo = function(o) {
var f = function() {
return o.length;
}
o
[1] = "b";
return f;
}(obj);

foo
(); // returns 2
obj
[1] = "b";
obj
[2] = "c";
foo
(); // returns 3

Mais uma vez, podemos começar raciocinando sobre como o tempo de execução resolve a variável o de . Como você provavelmente já sabe, essa variável o é igual à variável o de . E, uma vez que contém uma referência a um objeto, qualquer alteração nesse objeto será refletida na saída de . Isso explica por que a primeira chamada para agora retorna 2, enquanto anteriormente estava retornando 1.return o.length;var foo = function(o) {foo()foo()

Exemplo 3

Se você conseguiu chegar até aqui, este último pedaço de código não deve trazer nenhuma surpresa para você.

var obj = ["a"];

var foo = function() {
return obj.length;
}

foo
(); // returns 1
obj
[1] = "b";
obj
[2] = "c";
foo
(); // returns 3

O tempo de execução resolverá a variável obj de para ser a mesma que a variável obj de . Como resultado, qualquer mudança na variável obj terá um efeito na saída de .return obj.length;var obj = ["a"];foo()

Conclusão

Espero que este post tenha desmistificado um pouco os encerramentos. Repetidamente, mostramos como seguir alguns passos simples o levará a entender o comportamento deles. Basta ter em mente estas regras básicas e você deve estar pronto para começar:

  • se uma variável fechada contém um valor, o fechamento se vincula a essa variável
  • se uma variável fechada contém uma referência a um objeto, então o fechamento se vincula a esse objeto e pegará todas as alterações feitas nele

Idealmente, este se tornará meu post obrigatório para fornecer uma introdução aos encerramentos. Então, por favor, deixe-me saber qualquer sugestão que você possa ter para melhorar esta postagem.