Melhor maneira de obter nós filhos

233

Eu queria saber, o JavaScript oferece uma variedade de métodos para obter o primeiro elemento filho de qualquer elemento, mas qual é o melhor? Pelo melhor, quero dizer: mais compatível com vários navegadores, mais rápido, mais abrangente e previsível quando se trata de comportamento. Uma lista de métodos / propriedades que eu uso como alias:

var elem = document.getElementById('container');
var child = elem.children[0];
var child = elem.firstElementChild; // == children[0]

Isso funciona para os dois casos:

var child = elem.childNodes[0]; // or childNodes[1], see below

Isso é no caso de formulários ou <div>iteração. Se eu encontrar elementos de texto:

var child = elem.childNodes; // treat as NodeList
var child = elem.firstChild;

Tanto quanto eu posso trabalhar, firstChildusa o NodeList de childNodes, e firstElementChildusa children. Estou baseando essa suposição na referência MDN:

childNodeé uma referência ao primeiro elemento filho do nó do elemento ou nullse não houver um.

Eu estou supondo que, em termos de velocidade, a diferença, se houver, será quase nula, já que firstElementChildé efetivamente uma referência children[0]e o childrenobjeto já está na memória de qualquer maneira.

O que me joga, é o childNodesobjeto. Eu usei para dar uma olhada em um formulário, em um elemento de tabela. Enquanto childrenlista todos os elementos do formulário, childNodestambém parece incluir espaço em branco no código HTML:

console.log(elem.childNodes[0]);
console.log(elem.firstChild);

Ambos log <TextNode textContent="\n ">

console.log(elem.childNodes[1]);
console.log(elem.children[0]);
console.log(elem.firstElementChild);

Todos os log <input type="text"... >. Por quê? Eu entenderia que um objeto me permitiria trabalhar com o código HTML "bruto", enquanto o outro adere ao DOM, mas o childNodeselemento parece funcionar nos dois níveis.

Para voltar à minha pergunta inicial, meu palpite seria: se eu quero o objeto mais abrangente, childNodesé o caminho a percorrer, mas, devido à sua abrangência, pode não ser o mais previsível em termos de retorno do elemento que eu quero / esperar a qualquer momento. O suporte a vários navegadores também pode ser um desafio nesse caso, embora eu possa estar errado.

Alguém poderia esclarecer a distinção entre os objetos em questão? Se houver uma diferença de velocidade, ainda que insignificante, eu também gostaria de saber. Se eu estiver vendo tudo errado, sinta-se à vontade para me educar.


PS: Por favor, por favor, eu gosto de JavaScript, então sim, quero lidar com esse tipo de coisa. Respostas como "o jQuery lida com isso para você" não são o que estou procurando, portanto, não tag.

Elias Van Ootegem
fonte
50
>> por favor, por favor, eu gosto de JavaScript, então sim, eu quero lidar com esse tipo de coisa. respostas como 'jQuery lida com isso para você' não são o que eu estou procurando << upvotes para esta
david.barkhuizen

Respostas:

211

Parece que você está pensando demais. Você observou a diferença entre childNodese children, que childNodescontém todos os nós, incluindo os nós de texto que consistem inteiramente em espaço em branco, enquanto childrené uma coleção apenas dos nós filhos que são elementos. Isso é realmente tudo o que existe.

Não há nada imprevisível em nenhuma das coleções, embora haja alguns problemas a serem observados:

  • O IE <= 8 não inclui nós de texto somente em espaço em branco, childNodesenquanto outros navegadores
  • O IE <= 8 inclui nós de comentários, childrenenquanto outros navegadores têm apenas elementos

children, firstElementChilde amigos são apenas conveniências, apresentando uma exibição filtrada do DOM restrita a apenas elementos.

Tim Down
fonte
Imaginei isso, apesar de uma coisa ainda me incomodar: o nó de texto em espaço em branco que childNodes atende é na verdade apenas uma nova linha e algumas guias no código. Isso ocorre devido ao tipo de documento XHTML? isso pode ser resolvido colocando o espaço em branco para estruturar o código dentro das tags?
Elias Van Ootegem
@EliasVanOotegem: Isso não está relacionado ao doctype. Nós de texto em espaço em branco são sempre incluídos no DOM (exceto no IE <9) para HTML e XHTML, onde quer que apareçam na fonte. Geralmente é uma coisa boa, embora possa te surpreender se você não estiver preparado para isso.
Tim Baixo
Eu quis dizer se isso ajudaria a escrever minha marcação assim <td\n\t>content</td>. Como você pode fazer com o XML, para evitar que o espaço em branco em excesso seja considerado parte dos dados (ou DOM, nesse caso). Por que espaço em branco dentro de uma tag seria incluído no DOM?
Elias Van Ootegem
@EliasVanOotegem: Ah, entendo. O espaço em branco após o nome da marca dentro de uma marca é ignorado, portanto você pode fazer esse tipo de coisa. Vi essa técnica usada em layouts precisos para evitar que os navegadores adicionem pequenas lacunas no espaço em branco entre alguns tipos de elementos.
Tim Baixo
2
@Christophe: Ah, ponto justo, obrigado. Vou adicionar uma nota à minha resposta. Dado que se childrenoriginou no IE 4 mais de uma década antes de se tornar um padrão ou ser adotado por outros navegadores, você poderia argumentar que o IE <= 8 está correto e outros navegadores estão errados.
Tim Baixo
23

firstElementChild pode não estar disponível no IE <9 (apenas firstChild)

no IE <9 firstChild é o firstElementChild porque o MS DOM (IE <9) não está armazenando nós de texto vazios. Mas se você fizer isso em outros navegadores, eles retornarão nós de texto vazios ...

minha solução

child=(elem.firstElementChild||elem.firstChild)

isso dará o primeiro filho mesmo no IE <9

neu-rah
fonte
Se elem.firstChildnão for um nó de elemento, os resultados serão diferentes nos IEs antigos, porque elem.firstElementChildé sempre o nó do elemento.
Duri
Sinto muito, mas sei como obter o primeiro filho em todos os principais navegadores. O que eu quero saber é como os diferentes objetos são estruturados, para entender melhor as semelhanças e diferenças entre eles. Vou editar a minha pergunta mais tarde para fazer isso mais claro, desculpe pela confusão
Elias Van Ootegem
1
firstElementChildtambém não é necessariamente seguro em navegadores que não sejam o IE: o Firefox só começou a suportá-lo na versão 3.5.
Tim Baixo
isso está funcionando para chrome, IE9 e IE8, porque se eles tiverem o firstElementChild, a outra verificação não será executada; caso contrário (IE antigo), temos o firstChild, e é um nó de elemento do navegador que não possui firstElementChild (IE <9 ) ... ainda me pergunto sobre FF
neu-rah
1
Tim está correto, quando eu estava pesquisando material sobre estes objetos este mostrou-se, bom local para verificar de vez em quando
Elias Van Ootegem
20

A maneira de fazer vários navegadores é usar childNodespara obter NodeListe criar uma matriz de todos os nós com nodeType ELEMENT_NODE .

/**
 * Return direct children elements.
 *
 * @param {HTMLElement}
 * @return {Array}
 */
function elementChildren (element) {
    var childNodes = element.childNodes,
        children = [],
        i = childNodes.length;

    while (i--) {
        if (childNodes[i].nodeType == 1) {
            children.unshift(childNodes[i]);
        }
    }

    return children;
}

http://jsfiddle.net/s4kxnahu/

Isso é especialmente fácil se você estiver usando uma biblioteca de utilitários como o lodash :

/**
 * Return direct children elements.
 *
 * @param {HTMLElement}
 * @return {Array}
 */
function elementChildren (element) {
    return _.where(element.childNodes, {nodeType: 1});
}

Futuro:

Você pode usar querySelectorAllem combinação com a :scopepseudo-classe (corresponde ao elemento que é o ponto de referência do seletor):

parentElement.querySelectorAll(':scope > *');

No momento da redação deste artigo, isso :scopeé suportado no Chrome, Firefox e Safari.

Gajus
fonte
: o escopo foi removido da especificação . Devemos remover a seção Futuro ?
Thomas Marti
4

Apenas para adicionar outras respostas, ainda existem diferenças notáveis ​​aqui, especificamente ao lidar com <svg>elementos.

Eu usei ambos .childNodese .childrenpreferi trabalhar com os HTMLCollectionentregues pelo .childrengetter.

Hoje, no entanto, tive problemas com o IE / Edge com falha ao usar .childrenum <svg>. Embora .childrenseja suportado no IE em elementos HTML básicos, ele não é suportado em fragmentos de documento / documento ou elementos SVG .

Para mim, eu pude simplesmente pegar os elementos necessários, .childNodes[n]porque não tenho nós de texto estranhos para me preocupar. Você pode fazer o mesmo, mas, como mencionado acima, não se esqueça de que pode encontrar elementos inesperados.

Espero que isso seja útil para alguém coçando a cabeça tentando descobrir por que .childrenfunciona em outros lugares em seus js no IE moderno e falha nos elementos de documento ou SVG.

slothluvchunk
fonte
3

Ei, pessoal, não deixe o espaço em branco te enganar. basta testar isso em um navegador de console. use javascript nativo. Aqui está e exemplo com dois conjuntos 'ul' com a mesma classe. Você não precisa ter sua lista 'ul' em uma linha para evitar o espaço em branco; basta usar a contagem de matrizes para pular o espaço em branco.

Como chegar espaço em torno branco com querySelector()então childNodes[]js ligação violino: https://jsfiddle.net/aparadise/56njekdo/

var y = document.querySelector('.list');
var myNode = y.childNodes[11].style.backgroundColor='red';

<ul class="list">
    <li>8</li>
    <li>9</li>
    <li>100</li>
</ul>

<ul class="list">
    <li>ABC</li>
    <li>DEF</li>
    <li>XYZ</li>
</ul>
andre paradise
fonte
Não é o meu voto negativo, mas acho que alguém votou menos porque: a) Esta pergunta tem 4 anos e document.querySelectornão foi bem apoiada na época. b) A questão é basicamente perguntar quais são as diferenças entre childrene childNodes internamente , o que é mais confiável, quando escolher uma sobre a outra. e c) Porque, como demonstrada em questão: childNodes não incluem espaços em branco nós, childrennão ...
Elias Van Ootegem