Percorrer o DOM com Javascript e childNodes

Enquanto uma página é carregada, o navegador cria a árvore DOM do HTML. Usamos Javascript para acessar a interface DOM para criar efeitos, animações, manipular eventos e modificar o conteúdo. Ainda hoje é comum usar frameworks para fazer isso e também para reduzir a quantidade de código. Mas isso veio de uma época que não existia querySelector, classList e outras ferramentas. Uma simples interação, como acrescentar uma classe nos elementos com determinada classe demandava várias linhas de código e até expressão regular. Mas ainda assim, mesmo com essas novas ferramentas os frameworks ainda são de grande ajuda. Existem novas vertentes que omitem quase a zero nossa interação com o DOM, como é o caso do AngularJS. Mas o objetivo deste post não é falar sobre isso, mas sim ver, ou rever para muitos como percorrer o DOM da maneira antiga, da maneira oldSchool. Os benefícios estão mais para se você estiver criando uma nova ferramenta ou quiser conhecer como as coisas funcionam.

O primeiro que você precisa saber é que a árvore DOM é uma árvore de objetos relacionados. Cada nó tem acesso direto ao nó pai e aos nós filhos. O nó pai é um único elemento em parentNode e o nós filhos um array em childNodes. Um for é o suficiente para acessar todos os nós filhos.

for (var i=0; i < node.childNodes.length; i++) {
	var child = node.childNodes[i];
}

A variável node pode ser qualquer elemento da árvore. O for percorre todos os filhos. Então para percorrer os filhos dos filhos…

for (var i=0; i < node.childNodes.length; i++) {
	var child = node.childNodes[i];
	for (var j=0; j < child.childNodes.length; j++) {
		...
	}
}

Considerando essa abordagem, o seu programa estará limitado a quantidade de for em cadeia que você conseguir escrever. Parece uma péssima ideia. Não dá para prever toda a profundidade da árvore e esse método é um tanto insano. Lembra da recursão? Parece ser uma ótima ideia agora.

Recursão

Sem muita teoria, para termos recursão precisamos de uma função que execute ela mesmo.

function percorre(node) {
	for (var i=0; i < node.childNodes.length; i++) {
		percorre(node.childNodes[i]);
	}
}

percorre recebe um nó, percorre seus filhos e repassa para ele mesmo cada nó filho. E assim, para cada nó filho será feito a mesma coisa.

Então para percorrer todo body, você precisa apenas:

percorre(document.body);

TextNode

Os objetos do DOM não são apenas tags, tem textos, comentários e outros, mas que fogem do HTML (XML e SVG). Repare no markup abaixo.

<p>texto <br> mais texto</p>

Tem um nó <p>, texto e no meio do texto outro nó <br>. Esse texto, antes e depois de <br> também são objetos do DOM. O elemento <p> possui 3 childNodes.

node
p
	childNodes
	1 - texto (textNode)
	2 - <br> (elementNode)
	3 - mais texto (textNode)

Nós texto não possuem childNodes, então precisamos de um tratamento no código para evitar se aprofundar nesses nós. Para identificar o tipo de um nó, use nodeType. Mais abaixo tem uma lista com os nodeTypes do HTML.

function percorre(node) {
	if (node.nodeType == 1)
		for (var i=0; i < node.childNodes.length; i++) {
			percorre(node.childNodes[i]);
		}
	}
}

Você pode aproveitar essa informação para fazer tratamentos especiais por tipo de nó.

function percorre(node) {
	if (node.nodeType == 3) {
		// imprime todos os nós texto e seus tags
		console.log(node.parentNode.nodeName, node.nodeValue);
	}

	if (node.nodeType == 1) {
		for (var i=0; i < node.childNodes.length; i++) {
			percorre(node.childNodes[i]);
		}
	}
}

Os atributos de uma tag não são contemplados em childNodes, mas também são considerados objetos do DOM e também possuem nodeType. Se precisar interagir com os atributos, considere:

function percorre(node) {
	// atributo
	if (node.nodeType == 2) {
		...
	}
	// nó texto
	else if (node.nodeType == 3) {
		console.log(node.parentNode.nodeName, node.nodeValue);
	}

	// apenas elementos, ou tags
	if (node.nodeType == 1) {
		// percorre todos os atributos
		for (var j=0; j < node.attributes.length; j++) {
			percorre(node.attributes[j]);
		}

		// percorre todos nós filhos
		for (var i=0; i < node.childNodes.length; i++) {
			percorre(node.childNodes[i]);
		}
	}
}

Cachear ou não a quantidade de nós? Isso depende da sua aplicação. Se no meio da interação você criar novos nós, cachear length pode ser uma péssima ideia. Na próxima interação ao menos um elemento deixará de ser percorrido pois o cache tem o valor desatualizado de length. Sem cachear, ao criar um nó, childNodes é automaticamente atualizado e seu length também. Por outro lado, você vai iterar com esse novo elemento criado. Talvez seja a hora de aprimorar o retorno e a interação dessa função.

function percorre(node) {
	if (node.nodeType == 3) {
		var novo = new TextNode('Tem textNode a minha esquerda');
		node.parentNode.insertBefore(novo, node.nextSibling);
		return 2;
	}

	if (node.nodeType == 1) {
		for (var i=0; i < node.childNodes.length; ) {
			i += percorre(node.childNodes[i]);
		}
	}

	return 1;
}

O retorno de 2 faz pular a interação sobre o novo nó, acrescentando mais de 1 na variável de controle i. Você deve mudar o valor de retorno para o número de elementos criados somado mais um. Retornar zero vai fazer interagir novamente com o mesmo elemento. Tenha em mente que, dependendo do que você estiver criando talvez você vai preferir interagir com os novos nós criados.

Espero que você tenha gostado. Essa mesma lógica pode ser aplicada em outras árvores, como XML e SVG. Até a próxima!

Node types do HTML

nodeType Descrição
1 Elemento (tag)
2 Atributo de elemento
3 Texto (text node)
8 Comentário (comment node)

You may also like...

  • Franklin Javier

    Muito bem explicado (as usual).