O Google anunciou o lançamento de um portal chamado “Chrome Experiments” que compila uma série de games e visualizações experimentais desenvolvidos em JavaScript por profissionais em várias partes do mundo.
Vale apena ver, tem muitas coisas legais.
Site: http://www.chromeexperiments.com
Posts Tagged ‘JavaScript’
-
Novo lançamento da Google, confira o portal Google Chrome Experiments
March 19, 2009 by admin
Category JavaScript | Tags: browser,chrome,google,inteface,JavaScript | No Comments
-
Memory Leak
March 17, 2009 by admin
Contents
Identificação do problema
Quando desenvolvemos aplicativos com DHTML (Javascript, DOM e CSS) encontramos problemas para controlar a performance e os memory leaks. Uma vez identificados os padrões de códigos que causam esses problemas, começaremos a trabalhar de forma homogênea e sem a necessidade de retrabalho.
O problema não atinge só o Internet Explorer 6.0 (IE6). Ele pode acontecer com o Mozilla, Netscape e Opera. Apesar do IE6 ser o browser mais problemático hoje em dia. Muitos aplicativos, com determinado tempo de uso, começam a consumir uma quantidade impressionante de memória causando lentidão na máquina do usuário e refletindo na experiência que ele tem ao acessar o site. Muitas vezes a memória só é devolvida ao sistema operacional depois de fechar o browser.
Então o que acontece para existir o memory leak? Como é muito comum supor, o problema não está no garbage colector (GC) dos browsers. Pelo contrário. Eles funcionam muito bem se você tomar determinados cuidados na hora de programar. Considere o seguinte código:
function GCTest() { var x = new Array(1000).join(new Array(2000).join('XXXXX')); }Após esta função ser executada, toda a memória utilizada pela variável x é devolvida para o sistema operacional pelo GC do Javascript (COM). O mesmo acontece quando trabalhamos com elementos do DOM. Quando criamos uma tabela com HTML, não precisamos nos preocupar em remover essa tabela da memória após a execução da página.
<table> <tr><td>Lorem</td><td>Ipsum</td></tr> </table>
Quando trabalhamos com os “dois mundos”, o COM guarda uma referência para cada elemento. Se existe referência para um elemento no COM, a memória usada pela por ele não é liberada. O problema acontece quando temos uma referência cruzada, onde um objeto Javascript aponta para um objeto DOM e o DOM aponta de volta para o Javascript, e assim a memória usada nunca será liberada.
Padrões de memory leak
Inline script
Na declaração inline de um script na criação de um elemento HTML a referência que é criada para a função foo() não é eliminada depois que o código é executado. Isso cria uma referência circular e causa o memory leak.
function LeakMemory() { for (var i = 0; i < 5000; i++) {var parentDiv = document.createElement(‘
‘);} }
Nesse código criamos um novo elemento div sem a declaração do evento. O evento deve ser adicionado depois que o elemento já existe no DOM para evitar o memory leak. Note que nesse exemplo o número de iterações passa de 5.000 para 50.000, e mesmo assim a memória é liberada após a execução do escopo da função.
function LeakMemory() { for (var i = 0; i < 50000; i++) { var parentDiv = document.createElement('div'); } }Closures
Closures é um pattern muito utilizado em Javascript que permite o encapsulamento de funções. Leia mais sobre o assunto em Referências. Neste caso, adicionamos o evento onclick após a criação do elemento HTML.
function LeakMemory() { var parentDiv = document.createElement('div'); parentDiv.onclick = function() { foo(); }; }Quando criamos o elemento parentDiv e adicionamos o evento onclick dentro da função principal, criamos um closure. Ele guarda uma referência no elemento DOM (parentDiv) e o Javascript (função anônima).
Expando property
No código a seguir, criamos uma referência circular guardando a referência da variável global do Javascript que aponta para um elemento DOM dentro de uma propriedade do elemento DOM. O objeto Javascript myGlobalObject aponta para o objeto DOM (div) LeakDiv que tem a propriedade expandoProperty que aponta de volta para o elemento Javascript myGlobalObject.
var myGlobalObject; function SetupLeak() { myGlobalObject = document.getElementById('LeakedDiv'); document.getElementById('LeakedDiv').expandoProperty = myGlobalObject; }Encapsulator pattern
Esse caso é muito semelhante ao anterior só que a referência circular é feita guardando a referência do DOM dentro da função Javascript e a referência Javascript dentro da propriedade do objeto DOM. O objeto Javascript this.elementReference aponta para o objeto DOM (div) element que tem a propriedade expandoProperty que aponta de volta para o elemento Javascript.
function Encapsulator(element) { this.elementReference = element; element.expandoProperty = this; } function SetupLeak() { new Encapsulator(document.getElementById('LeakedDiv')); }Event listener
Esse é o caso mais comum de memory leak com closures, onde o evento onclick faz a referência circular por usar uma função anônima dentro do mesmo escopo da criação do listener.
window.onload = function() { var obj = document.getElementById('LeakedDiv'); obj.onclick = function(e) { ... lógica ... }; }Cross Page Leak
Aqui temos um caso que é muito difícil de identificar. Nesse caso, a ordem de inserção dos novos elementos na árvore do DOM influencia. Na criação do parentDiv e do childDiv foi usado inline script e quando adicionamos os elementos na página ele perde a referência porque os elementos apontam para funções anônimas.
function LeakMemory() { var hostElement = document.getElementById('hostElement'); for (i = 0; i < 5000; i++) {var parentDiv = document.createElement(‘
‘);
var childDiv = document.createElement(‘‘);parentDiv.appendChild(childDiv); hostElement.appendChild(parentDiv); hostElement.removeChild(parentDiv); parentDiv.removeChild(childDiv); parentDiv = null; childDiv = null; } hostElement = null; }
Novamente, a melhor opção é trabalharmos com funções fora do escopo da criação dos elementos e, se necessário, ao inserir no DOM sempre fazermos a inserção do elemento pai e depois o elemento filho como é mostrado no exemplo seguinte.
function LeakMemory() { var hostElement = document.getElementById('hostElement'); for (i = 0; i < 5000; i++) {var parentDiv = document.createElement(‘
‘);
var childDiv = document.createElement(‘‘);hostElement.appendChild(parentDiv); parentDiv.appendChild(childDiv); parentDiv.removeChild(childDiv); hostElement.removeChild(parentDiv); parentDiv = null; childDiv = null; } hostElement = null; }
Boas práticas
Referências ao DOM
É recomendável que ao criar uma variável Javascript que aponte para um elemento DOM ela seja desalocada logo que deixar de ser usada. Para isso atribuímos null à variável. Essa regra se aplica tanto para variáveis privadas como para as privilegiadas.
function domRefecente() { var header = document.getElementById('headerDiv'); var footer = document.getElementById('footerDiv'); this.div = document.createElement('div'); ... lógica ... header = null, footer = null, this.div = null; }Quando é necessário guardar um objeto DOM no Javascript para utilização futura, SEMPRE guarde o id do objeto ao invés de uma referência para ele.
function domRefecente() { var div = document.createElement('div'); div.id = 'meuDiv'; var divId = div.id; div = null; }Eventos
Não usar funções anônimas para os eventos. Exemplo da correta declaração de um evento.
function addListener() { var elm = document.getElementById('myDiv'); elm.onclick = handleClick; elm = null; }function handleClick(e) { ... lógica ... }Ao atribuir um novo evento a um elemento, ele já deve existir na árvore do DOM. Exemplo da correta atribuição de evento a um novo elemento que está sendo criado via Javascript.
function newElement() { var elm = document.createElement('div'); document.appendChild(elm); elm.onclick = handleClick; elm = null; }function handleClick() { ... lógica ... }Quando um elemento é removido do DOM via Javascript, todos os eventos cadastrados nele devem ser removidos para evitar o memory leak.
function createElements() { var ul = document.getElementById('menu'); for (var i = 0; i < 5000; i++) { var li = document.createElement('li'); li.id = 'menu' + i; ul.appendChild(li); li = null; } addListener(); ul = null; }function addListener() { for (var i = 0; i < 5000; i++) { var li = document.getElementById('menu' + i); li.onclick = handleClick; li = null; } }function handleClick(e) {
... lógica ...
}
function removeElements() { for (var i = 0; i < 5000; i++) { var li = document.getElementById('menu' + i); li.onclick = null; li.parentChild.removeChild(li); li = null; } }Criando elementos no DOM
A ordem com que os elementos são inseridos no DOM via Javascript influencia na performance e na criação de memory leaks. O ideal é termos uma outra função fora do escopo da função usada para a criação dos elementos onde serão atribuídos os eventos a esses elementos.
Quando estamos inserindo uma série de elementos no DOM, a cada appendChild o browser renderiza esse elemento juntamento com todos os filhos do elemento (elemento pai) aonde estamos inserindo ele. O que isso significa? Quando temos um div com vários div’s dentro dele, ao inserir um novo div, todos os anteriores serão renderizados novamente, consumindo uma quantidade enorme de tempo proporcional a quantidade de elementos no div pai.
Nesse caso usamos a técnica de criar um elemento temporário, adicionar todos os div’s filhos dentro dele e só então adicioná-los ao div pai.
function addDiv() { var divPai = document.getElementById('divPai'); var divTmp; for (var i = 0; i < 5000; i++) { var divFilho = document.createElement('div'); divTmp.appendChild(divFilho); divFilho = null; } divPai.appendChild(divTmp); divPai = null, divTmp = null; }Existe o caso em que precisamos adicionar o evento nos elementos na hora da sua criação. Para tornar isso possível, podemos inverter a ordem da inclusão dos elementos como mostrado no item Cross Page Leak.
Conclusão
Com um pouco de atenção e conhecimento podemos evitar muita dor de cabeça no futuro. Facilitando a manutenção do código e o acesso dos usuários aos nossos sites. O memory leak é apenas um dos problemas que temos de nos preocupar na hora de criar um aplicativo web. Por isso que um pouco de planejamento antes de começar a codificar evita muitas horas de depuração de código e retrabalho.
Programas
Veja no item específico da lista de Softwares para Interface #Memory Leak.
Referências
- Javascript Closures
- Memory Leakage in Internet Explorer – revisited
- Memory leak patterns in JavaScript
- IE: where’s my memory?
- DHTML Leaks Like a Sieve
- Javascript memory leaks
Category JavaScript | Tags: DOM,inteface,JavaScript,memory leak | 1 Comment