Três dicas de performance javascript

09-07-2020

Neste post vamos aprender como evitar problemas de memory leaks em javascript no browser, evitando crashs indesejados.

Memory leaks, traduzido ao pé da letra significa vazamento de memória, o que, na prática significa que o Sistema operacional não está conseguindo gerenciar a memória utilizada de forma eficiente, o que pode fazer com que o Sistema fique lento ou até mesmo a aplicação (browser) trave.

É verdade que na maioria das vezes em que programamos em javascript para rodar no browser não precisamos nos preocupar com esse tipo de problema pois há um mecanismo chamado garbage collector que “limpa” a memória de tempos em tempos quando há possibilidade: uma variável que não é mais utilizada, uma function que não será mais executada, etc.

Se em algumas linguagens o programador precisa explicitamente dizer ao Sistema operacional quando determinada parte da memória pode ser limpa, há outras linguagens, como o javascript, em que o garbage collector faz o trabalho sujo pra gente. Mas nem sempre esse serviço pode ser feito de forma eficiente. E a raiz disso é o que chamamos referências indesejadas.

1 referência indesejada: objeto window (escopo global)

Essa é fácil de entender. Para que o garbage collector possa limpar uma variavel na memoria, nenhuma referencia à essa variavel pode existir no código, certo? O problema com variaveis e funções no escopo global é que esse tipo de dado pode ser lido ou executado em qualquer momento não previsto (click de um botão, tecla pressionada, retorno de uma API etc). Tudo o que é global não pode ser limpo. Pois tudo o que pode ser lido ou executado por outra parte do código precisa estar disponível.

De tempos em tempos o garbage collector procura por referencias na memória que não podem mais ser alcançadas por nenhuma outra parte do código e por esse motivo são marcadas como lixo e podem ser removidas.

Evitar poliur o escopo global é fácil, mas as vezes fazemos isso sem querer, veja:

1
2
3
4
5
function foo() {
console.log(this) //nesse caso, é Window
this.variable = " global";
}
foo()

Para evitar esse tipo de problema mascarado de normalidade, use “use stricit”.

1
2
3
4
5
6
function foo() {
"use strict"
console.log(this) //nesse caso, é undefiend
this.variable = " global"; // vai gerar um erro, mas o escopo global está intacto
}
foo()

setInterval

Todos nós que trabalhamos com javascript já precisamos criar uma função que fosse executada de tempos em tempos com o uso do setInterval. E não há nada de errado com isso, desde que você não se esqueça de limpar seu intervalo quando não for mais necessário.

1
2
3
4
5
let x = 0
setInterval(function(){
x++
if(x < 10) console.log(x)
}, 1000)

É fácil perceber nesse exemplo que será mostrado no console os números de 1 a 9 a cada 1 segundo. Porém, a função anônima continuará sendo executada enquanto a página estiver aberta e por esse motivo essa mesma função anônima não poderá ser limpa pelo garbage collector.

Para corrigir isso, devemos sempre nos lembrar de limpar o interval com o uso do clearInterval

1
2
3
4
5
6
let interval = setInterval(function(){
x++
if(x < 10) return console.log(x)
interval = null
clearInterval(interval)
}, 1000)

Tente levar esse exemplo para um caso mais real, como um cronômetro com três botões, iniciar, pausar e parar.

Armazenar referencias do DOM

As vezes queremos usar os métodos convenientes de uma array como filter ou map em uma coleção de objetos do DOM (nodeList ou HTMLCollection). Nesses casos precisamos armazenar as referencias do DOM numa array

1
2
3
4
5
const dom = document.querySelectorAll('.item')
const arr = []
for (let i = 0; i < dom.length; i++) {
arr[i] = dom[i];
}

Nesse ponto você pode estar me chamando de antiquadro e se questionando se os meus conhecimentos de javascript são ultrapassados. Você pode ter pensado no seguinte código:

1
2
const dom = document.querySelectorAll('.item')
const arr = [...dom]

Muito mais elegante. Eu concordo. Mas eu pergunto: o seu código vai passar pelo babel?

tela do babel mostrando que o spread operator vira um loop for

Vou colar o trecho do código transpilado que nos interessa para esse exemplo

1
2
3
4
5
6
7
function _arrayLikeToArray(arr, len) { 
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) {
arr2[i] = arr[i];
}
return arr2;
}

O bom e velho loop for é o que o babel faz por baixo dos panos.

Mas voltando ao tópico do post, ao armazenar referências do DOM numa estrutura de dados como uma array ou um objeto, criamos uma referência para aquele elemento do DOM.

Se removermos esse elemento da arvore do DOM precisamos lembrar de remover as referencias junto, caso contrário esta referência continuará ocupando espaço na memória, mesmo que o elemento em si já não exista mais na página.

Há várias maneiras de fazer isso e a abordagem escolhida vai depender de caso a caso. Apenas tenha em mente que, se criou uma array com elementos do DOM e por algum motivo qualquer a árvore do DOM foi atualizada, atualize também a sua array com o DOM atualizado. Sendo assim, o espaço na memória que armazenava aquele objeto do DOM pode ser limpo.

Conclusão

Então é isso. Algumas dicas para você ter em mente quando precisar dar um gás na performance da sua aplicação web.


Comentários: