RSS Feed

Posts Tagged ‘JavaScript’

  1. Novo lançamento da Google, confira o portal Google Chrome Experiments

    March 19, 2009 by admin

    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


  2. 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 memory leaks