JavaScript* Patterns - Estático

Tweet Like

O que você faz quando você quer a funcionalidade de uma variável estática em Javascript ? Como discutido previamente, usar uma variável global ao invés de uma estática não é uma boa ideia. Alguém pode até ficar tentado em usar alguma regra para nomenclatura de variáveis, para distinguir as globais que se pretende usar como estáticas das outras (ex. '_static_i'), mas isso realmente não resolve o problema, porque duas funções distintas podem terminar com variáveis estáticas similares, com uma sobrescrevendo a outra. O melhor a fazer é evitar variáveis globais ao menos que você realmente precise delas.

Dmitry foi gentil o suficiente para postar uma sugestão como esta:

function f(x) {
  if (typeof f.i === 'undefined') {
    f.i = 0;
  }
  return ++f.i+x;
}

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Existe muita coisa interessante sobre este código. Primeiro é o fato de que, como ele destacou, tudo é um objeto em javascript, incluindo a função f, e portanto você pode adicionar atributos como 'f.i'. Outro é o uso do operador typeof e do 'undefined', que é um bom truque para evitar uma exceção. Tudo isso é matéria prima para um futuro post aqui no blog, mas eu gostaria de focar agora na solução para resolver o problema da variável estática

A sugestão do Dimitry é sólida. Ela atende a todos os requisitos. A variável estática é inicializada uma vez, neste caso na primeira vez em que a função é chamada. Ela retém seu valor entre uma chamada e outra. Sobre o escopo, bem... esta é uma questão interessante que eu pretendo deixar aberta por enquanto.

As desvantagens são, na minha cabeça, mais estéticas. A principal delas para mim é ter que adicionar 'f.' no início do que deveria ser o nome da minha variável. Eu suponho que se tivesse aprendido a usar um editor moderno, eu talvez nem percebesse isso, mas sendo um velho geek, eu continuo preso ao 'vi' (aka 'vim'), e preciso digitar praticamente cada caractere do meu código. Além disso, meus anos em análise de performance me tornaram um chato sobre branches (ramos), mas na verdade em Javascript existem tantos ramos acontecendo debaixo do capô, que eu duvido que mais um 'if' no início da função faria alguma diferença. Mas ainda assim, fica a questão sobre o escopo

Enquanto 'i' está associado a função 'f', nomeando uma variável global como 'i' não vai interferir nela, mas o que acontece é que mesmo assim ela ainda está acessível de fora do escopo da função. Eu ainda posso me referir a 'f.i' para ler e atualizar o seu valor:

> f(0)     // i = 1
1
> f(33)    // i = 2
35
> f(33)    // i = 3
36
> f.i=-25  // i = -25
-25
> f(0)     // i = -24
-24
> f(33)    // i = -23
10
>

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Agora fica mais difícil para sobrescrever a variável acidentalmente, portanto esta ainda é uma abordagem para programas bem comportados, e a habilidade de atualizar o valor de 'i' deve ser usada de forma adequada em circunstâncias específicas, mas existe uma outra abordagem que evita totalmente estes problemas.

Abrindo um rápido parênteses primeiro, quando você aprende Javascript, ao menos da forma pela qual eu aprendi, uma das primeiras coisas que aprende é como declarar uma função:

function f(x) { return x+1 }

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Isto está certo, porém o Javascript também te permite declarar funções "Anônimas", que não tem um nome. Muitas linguagens possuem uma funcionalidade parecida, algumas vezes chamadas de "funções Lambda", um termo que remete à consagrada ancestral de todas as linguagens funcionais, o "Lisp", que por sua vez tomou o termo emprestado do "Cálculo Lambda", mas eu discordo. Que vantagem existe em uma função anônima ? Bem, ela simplifica bastante a sua citação :-). Geralmente é usada em lugares onde você não precisa de um nome. Você vai vê-la com frequência em Javascript como uma forma um pouco diferente de definir uma função:

var f = function (x) {return x+1}

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Para muitos propósitos, isto é o mesmo que a declaração de função padrão, mas ele ajuda a pensar fora da caixa (ao menos se você for um velho programador C, dono de uma caixa arrumadinha que começa a mostrar alguma idade). No nosso caso, podemos usar uma função anônima que é chamada apenas uma vez para manter o escopo da nossa variável "estática", e depois disso (que é um inicializador), podemos retornar a função real que nos interessa. Eu não posso ser creditado por esta ideia, mas terminamos com algo do tipo:

var f = function () {
  var i = 0

  return function (x) {
    return ++i+x;
  }
}()

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Se você é como eu, vai precisar ver isso algumas vezes até entender de verdade, portanto observe com cuidado. Existem na verdade duas funções sendo declaradas aqui. A princípio, parece que f está sendo atribuída à uma função anônima externa, até que você percebe um par de parêntesis aparentemente perdidos no final da declaração da função. Isso cria uma função anônima, e imediatamente a chama. O único propósito desta função é inicializar a variável "estática" 'i' , e retornar a função interna que realmente faz o trabalho que nos interessa. Esta função interna é o que é atribuída ao 'f', então quando chamamos 'f', provemos um parâmetro 'x' e ela incrementa 'i', soma ele ao 'x' e retorna o resultado. 'i' existe somente dentro do escopo da função de inicialização, que não é mais acessível de lugar algum, portanto ninguém pode bagunçar com nosso 'i'. Mesmo se o inicializador fosse acessível, a variável 'i' não é uma propriedade de 'f', portanto não é algo que alguém do lado de fora de 'f' possa bagunçar.

Agora você pode imaginar que esta técnica e técnicas similares a esta podem ser usadas para todas as funcionalidades da linguagem, e você está certo. Se você procurar um pouco por código em Javascript na web, vai ver padrões como este em diversos lugares. Se você é como eu, deve ter queimado um pouco de fosfato para entender os motivos que nos levaram a utilizar um método de contorno desses para declarar uma função, e agora você pode começar a ver o motivo.

Пожалуйста, обратитесь к странице Уведомление об оптимизации для более подробной информации относительно производительности и оптимизации в программных продуктах компании Intel.