Por que jQuery ou um método DOM, como getElementById, não encontra o elemento?

483

Quais são os possíveis motivos para document.getElementById, $("#id")ou qualquer outro método DOM / seletor jQuery não encontrar os elementos?

Exemplos de problemas incluem:

  • jQuery silenciosamente falhando ao vincular um manipulador de eventos
  • jQuery "getter" métodos ( .val(), .html(), .text()) retornandoundefined
  • Um método DOM padrão retornando, nullresultando em qualquer um dos vários erros:

TypeError não capturado: Não é possível definir a propriedade '...' nulo TypeError não capturado: Não é possível ler a propriedade '...' nulo

As formas mais comuns são:

TypeError não capturado: não é possível definir a propriedade 'onclick' de null

TypeError não capturado: Não é possível ler a propriedade 'addEventListener' de null

TypeError não capturado: não é possível ler a propriedade 'style' de null

Felix Kling
fonte
32
Muitas perguntas são feitas sobre o motivo de um determinado elemento DOM não ser encontrado e o motivo geralmente é que o código JavaScript é colocado antes do elemento DOM. Pretende-se que seja uma resposta canônica para esse tipo de perguntas. É wiki da comunidade, então fique à vontade para aprimorá-lo .
Felix Kling

Respostas:

481

O elemento que você estava tentando encontrar não estava no DOM quando o script foi executado.

A posição do seu script dependente de DOM pode ter um efeito profundo sobre seu comportamento. Os navegadores analisam documentos HTML de cima para baixo. Os elementos são adicionados ao DOM e os scripts são (geralmente) executados conforme são encontrados. Isso significa que a ordem é importante. Normalmente, os scripts não conseguem encontrar elementos que aparecem mais tarde na marcação, porque esses elementos ainda precisam ser adicionados ao DOM.

Considere a seguinte marcação; o script 1 não consegue encontrar o <div>script while 2 bem-sucedido:

<script>
  console.log("script #1: %o", document.getElementById("test")); // null
</script>
<div id="test">test div</div>
<script>
  console.log("script #2: %o", document.getElementById("test")); // <div id="test" ...
</script>

Então o que você deveria fazer? Você tem algumas opções:


Opção 1: mova seu script

Mova seu script para baixo na página, pouco antes da etiqueta do corpo de fechamento. Organizado dessa maneira, o restante do documento é analisado antes da execução do seu script:

<body>
  <button id="test">click me</button>
  <script>
    document.getElementById("test").addEventListener("click", function() {
      console.log("clicked: %o", this);
    });
  </script>
</body><!-- closing body tag -->

Nota: A colocação de scripts na parte inferior geralmente é considerada uma prática recomendada .


Opção 2: jQuery ready()

Adie seu script até que o DOM seja completamente analisado, usando :$(handler)

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(function() {
    $("#test").click(function() {
      console.log("clicked: %o", this);
    });
  });
</script>
<button id="test">click me</button>

Nota: Você pode simplesmente vincular a DOMContentLoadedou mas cada uma tem suas ressalvas. O jQuery's oferece uma solução híbrida.window.onloadready()


Opção 3: Delegação de Eventos

Eventos delegados têm a vantagem de poder processar eventos de elementos descendentes que são adicionados ao documento posteriormente.

Quando um elemento gera um evento (desde que seja um evento de bolhas e nada interrompa sua propagação), cada pai na ancestralidade desse elemento também recebe o evento. Isso nos permite anexar um manipulador a um elemento existente e amostrar eventos à medida que surgem dos descendentes ... mesmo aqueles adicionados após o anexador. Tudo o que precisamos fazer é verificar o evento para ver se ele foi gerado pelo elemento desejado e, se houver, executar nosso código.

O jQuery's on()executa essa lógica para nós. Simplesmente fornecemos um nome de evento, um seletor para o descendente desejado e um manipulador de eventos:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(document).on("click", "#test", function(e) {
    console.log("clicked: %o",  this);
  });
</script>
<button id="test">click me</button>

Nota: Normalmente, esse padrão é reservado para elementos que não existiam no momento do carregamento ou para evitar anexar uma grande quantidade de manipuladores. Também vale ressaltar que, enquanto eu anexei um manipulador document(para fins demonstrativos), você deve selecionar o ancestral confiável mais próximo.


Opção 4: O deferatributo

Use o deferatributo de <script>.

[ defer, um atributo booleano] é definido para indicar a um navegador que o script deve ser executado após a análise do documento, mas antes da inicialização DOMContentLoaded.

<script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>

Para referência, aqui está o código desse script externo :

document.getElementById("test").addEventListener("click", function(e){
   console.log("clicked: %o", this); 
});

Nota: O deferatributo certamente parece uma bala mágica, mas é importante estar ciente das advertências ...
1. defersó podem ser usadas para scripts externos, ou seja, aqueles que possuem um srcatributo.
2. esteja ciente do suporte ao navegador , ou seja: implementação de bugs no IE <10

cânone
fonte
Agora é 2020 - "Colocar scripts na parte inferior" ainda é considerado uma prática recomendada? (Hoje em dia) coloco todos os meus recursos no <head>e uso deferem scripts (não preciso oferecer suporte a navegadores incompatíveis antigos)
Stephen P
Sou um grande defensor da mudança para navegadores modernos. Dito isto, essa prática realmente não me custa nada e funciona em todos os lugares. Por outro lado, ambos defere asynctêm um suporte bastante amplo, mesmo voltando a versões muito mais antigas do navegador. Eles têm a vantagem adicional de sinalizar de forma clara e explícita o comportamento pretendido. Lembre-se de que esses atributos funcionam apenas para scripts que especificam um srcatributo, ou seja: scripts externos. Pese os benefícios. Talvez use os dois? Sua chamada.
canon
146

Curto e simples: porque os elementos que você está procurando ainda não existem no documento.


Para o restante desta resposta, usarei getElementByIdcomo exemplo, mas o mesmo se aplica a getElementsByTagName, querySelectore a qualquer outro método DOM que selecione elementos.

Razões possíveis

Há duas razões pelas quais um elemento pode não existir:

  1. Um elemento com o ID passado realmente não existe no documento. Você deve verificar se o ID para o qual você passa getElementByIdrealmente corresponde ao ID de um elemento existente no HTML (gerado) e se não digitou o ID incorretamente (os IDs diferenciam maiúsculas de minúsculas !).

    Aliás, na maioria dos navegadores contemporâneos , que implementam querySelector()e querySelectorAll()métodos, a notação no estilo CSS é usada para recuperar um elemento por id, por exemplo document.querySelector('#elementID'):, em oposição ao método pelo qual um elemento é recuperado por seu idsub document.getElementById('elementID'); no primeiro, o #personagem é essencial; no segundo, levaria o elemento a não ser recuperado.

  2. O elemento não existe no momento em que você chama getElementById.

O último caso é bastante comum. Os navegadores analisam e processam o HTML de cima para baixo. Isso significa que qualquer chamada para um elemento DOM que ocorra antes que o elemento DOM apareça no HTML falhará.

Considere o seguinte exemplo:

<script>
    var element = document.getElementById('my_element');
</script>

<div id="my_element"></div>

A divaparece após o script. No momento em que o script é executado, o elemento não existe ainda e getElementByIdirá retornar null.

jQuery

O mesmo se aplica a todos os seletores com jQuery. O jQuery não encontrará elementos se você digitou incorretamente o seletor ou está tentando selecioná-los antes que eles realmente existam .

Um toque adicional é quando o jQuery não é encontrado porque você carregou o script sem protocolo e está sendo executado no sistema de arquivos:

<script src="//somecdn.somewhere.com/jquery.min.js"></script>

essa sintaxe é usada para permitir que o script seja carregado via HTTPS em uma página com protocolo https: // e para carregar a versão HTTP em uma página com protocolo http: //

Tem o efeito colateral lamentável de tentar e não carregar file://somecdn.somewhere.com...


Soluções

Antes de fazer uma chamada getElementById(ou qualquer método DOM), verifique se os elementos que você deseja acessar existem, ou seja, o DOM está carregado.

Isso pode ser garantido simplesmente colocando seu JavaScript após o elemento DOM correspondente

<div id="my_element"></div>

<script>
    var element = document.getElementById('my_element');
</script>

nesse caso, você também pode colocar o código imediatamente antes da tag body de fechamento ( </body>) (todos os elementos DOM estarão disponíveis no momento em que o script for executado).

Outras soluções incluem ouvir os eventos load [MDN] ou DOMContentLoaded [MDN] . Nesses casos, não importa onde, no documento, você coloca o código JavaScript, basta lembrar de colocar todo o código de processamento DOM nos manipuladores de eventos.

Exemplo:

window.onload = function() {
    // process DOM elements here
};

// or

// does not work IE 8 and below
document.addEventListener('DOMContentLoaded', function() {
    // process DOM elements here
});

Consulte os artigos em quirksmode.org para obter mais informações sobre manipulação de eventos e diferenças no navegador.

jQuery

Primeiro, verifique se o jQuery está carregado corretamente. Use as ferramentas de desenvolvedor do navegador para descobrir se o arquivo jQuery foi encontrado e corrija o URL se não estiver (por exemplo, adicione o esquema http:ou https:no início, ajuste o caminho etc.)

Ouvir os eventos load/ DOMContentLoadedé exatamente o que o jQuery está fazendo com .ready() [docs] . Todo o seu código jQuery que afeta o elemento DOM deve estar dentro desse manipulador de eventos.

De fato, o tutorial do jQuery afirma explicitamente:

Como quase tudo o que fazemos ao usar o jQuery lê ou manipula o modelo de objeto de documento (DOM), precisamos ter certeza de que começaremos a adicionar eventos etc. assim que o DOM estiver pronto.

Para isso, registramos um evento pronto para o documento.

$(document).ready(function() {
   // do stuff when DOM is ready
});

Como alternativa, você também pode usar a sintaxe abreviada:

$(function() {
    // do stuff when DOM is ready
});

Ambos são equivalentes.

Felix Kling
fonte
1
Valeria a pena alterar o último trecho do readyevento para refletir a sintaxe preferida "mais recente" ou é melhor deixá-lo como está para que funcione em versões mais antigas?
Tieson T.
1
Para jQuery, vale a pena também adicionando em alternativa $(window).load()(e alternativas possivelmente sintáticas para o $(document).ready(), $(function(){})Ele parece estar relacionado, mas ele? Sente um pouco tangente ao ponto que você está fazendo.
David diz Reintegrar Monica
@ David: Bom ponto com .load. Em relação às alternativas de sintaxe, elas podem ser consultadas na documentação, mas como as respostas devem ser independentes, pode valer a pena adicioná-las. Por outro lado, tenho certeza de que esse trecho específico do jQuery já foi respondido e provavelmente está coberto por outras respostas, e a ligação a ele pode ser suficiente.
Felix Kling
1
@ David: Eu tenho que revisar: Aparentemente .loadestá obsoleto: api.jquery.com/load-event . Não sei se é apenas para imagens ou windowtambém.
Felix Kling
1
@ David: Não se preocupe :) Mesmo se você não tiver certeza, é bom que você mencionou. Provavelmente adicionarei apenas alguns trechos de código simples para mostrar o uso. Obrigado pela sua contribuição!
Felix Kling
15

Razões pelas quais os seletores baseados em ID não funcionam

  1. O elemento / DOM com o ID especificado ainda não existe.
  2. O elemento existe, mas não está registrado no DOM [no caso de nós HTML anexados dinamicamente a partir das respostas do Ajax].
  3. Mais de um elemento com o mesmo ID está presente, o que está causando um conflito.

Soluções

  1. Tente acessar o elemento após sua declaração ou, alternativamente, use coisas como $(document).ready();

  2. Para elementos provenientes de respostas do Ajax, use o .bind()método jQuery. As versões mais antigas do jQuery tinham .live()o mesmo.

  3. Use as ferramentas [por exemplo, plug-in do desenvolvedor da web para navegadores] para encontrar IDs duplicados e removê-los.

sumit
fonte
13

Como o @FelixKling apontou, o cenário mais provável é que os nós que você está procurando ainda não existam.

No entanto, as práticas de desenvolvimento modernas geralmente podem manipular elementos do documento fora da árvore de documentos com DocumentFragments ou simplesmente desanexar / reconectar os elementos atuais diretamente. Essas técnicas podem ser usadas como parte do modelo de JavaScript ou para evitar operações excessivas de repintura / refluxo enquanto os elementos em questão estão sendo fortemente alterados.

Da mesma forma, a nova funcionalidade "Shadow DOM" lançada nos navegadores modernos permite que os elementos façam parte do documento, mas não possam ser consultados por document.getElementById e todos os seus métodos irmãos (querySelector, etc.). Isso é feito para encapsular a funcionalidade e ocultá-la especificamente.

Novamente, porém, é mais provável que o elemento que você está procurando simplesmente não esteja (ainda) no documento, e você deve fazer o que Felix sugere. No entanto, você também deve estar ciente de que essa é cada vez mais a única razão pela qual um elemento pode ser desconectado (temporariamente ou permanentemente).

Nathan Bubna
fonte
13

Se o elemento que você está tentando acessar estiver dentro de um iframee você tentar acessá-lo fora do contexto, iframeisso também fará com que ele falhe.

Se você deseja obter um elemento em um iframe, pode descobrir como aqui .

George Mulligan
fonte
7

Se a ordem de execução do script não for o problema, outra causa possível do problema é que o elemento não está sendo selecionado corretamente:

  • getElementByIdrequer que a sequência passada seja o ID literalmente , e nada mais. Se você prefixar a sequência passada com a #e o ID não iniciar com a #, nada será selecionado:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
  • Da mesma forma, para getElementsByClassName, não prefixe a sequência passada com um .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • Com querySelector, querySelectorAll e jQuery, para combinar um elemento com um nome de classe específico, coloque um .diretamente antes da classe. Da mesma forma, para corresponder um elemento a um ID específico, coloque um #diretamente antes do ID:

    <div class="baz"></div>
    // Error, selected element will be null:
    document.querySelector('baz')
    $('baz')
    // Fix:
    document.querySelector('.baz')
    $('.baz')

    As regras aqui são, na maioria dos casos, idênticas às dos seletores de CSS e podem ser vistas em detalhes aqui .

  • Para corresponder a um elemento que possui dois ou mais atributos (como dois nomes de classe ou um nome de classe e um data-atributo), coloque os seletores para cada atributo próximos um do outro na cadeia de seleção, sem um espaço para separá-los (porque um espaço indica o seletor descendente ). Por exemplo, para selecionar:

    <div class="foo bar"></div>

    use a string de consulta .foo.bar. Selecionar

    <div class="foo" data-bar="someData"></div>

    use a string de consulta .foo[data-bar="someData"]. Para selecionar o <span>abaixo:

    <div class="parent">
      <span data-username="bob"></span>
    </div>

    use div.parent > span[data-username="bob"].

  • Letras maiúsculas e ortografia são importantes para todos os itens acima. Se a capitalização for diferente ou a ortografia for diferente, o elemento não será selecionado:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • Você também precisa garantir que os métodos tenham a capitalização e ortografia adequadas. Use um dos seguintes:

    $(selector)
    document.querySelector
    document.querySelectorAll
    document.getElementsByClassName
    document.getElementsByTagName
    document.getElementById

    Qualquer outra ortografia ou capitalização não funcionará. Por exemplo, document.getElementByClassNamelançará um erro.

  • Certifique-se de passar uma string para esses métodos seletores. Se você passar algo que não é uma string para querySelector, getElementById, etc, é quase certo que não vai funcionar.

CertainPerformance
fonte
0

Quando tentei seu código, ele funcionou.

O único motivo pelo qual seu evento não está funcionando pode ser que seu DOM não estava pronto e seu botão com o ID "event-btn" ainda não estava pronto. E seu javascript foi executado e tentou vincular o evento a esse elemento.

Antes de usar o elemento DOM para ligação, esse elemento deve estar pronto. Existem muitas opções para fazer isso.

Opção1: você pode mover o código de ligação do evento para o evento ready do documento. Gostar:

document.addEventListener('DOMContentLoaded', (event) => {
  //your code to bind the event
});

Opção2: você pode usar o evento de tempo limite, para que a ligação seja atrasada por alguns segundos. gostar:

setTimeout(function(){
  //your code to bind the event
}, 500);

Opção 3: mova sua inclusão de javascript para a parte inferior da sua página.

Espero que isso ajude você.

Jatinder Kumar
fonte
@ Willdomybest18, por favor, verifique esta resposta
Jatinder Kumar 18/04
-1

o problema é que o elemento dom 'speclist' não é criado no momento em que o código javascript está sendo executado. Então, coloquei o código javascript dentro de uma função e chamei essa função no evento body onload.

function do_this_first(){
   //appending code
}

<body onload="do_this_first()">
</body>
Mia Davis
fonte
-1

Minha solução foi a de não usar o id de um elemento âncora: <a id='star_wars'>Place to jump to</a>. Aparentemente, o blazor e outras estruturas de spa têm problemas para se ancorar na mesma página. Para contornar isso eu tive que usar document.getElementById('star_wars'). No entanto, este não funcionou até que eu ponha o id em um elemento de parágrafo em vez disso: <p id='star_wars'>Some paragraph<p>.

Exemplo usando bootstrap:

<button class="btn btn-link" onclick="document.getElementById('star_wars').scrollIntoView({behavior:'smooth'})">Star Wars</button>

... lots of other text

<p id="star_wars">Star Wars is an American epic...</p>
goku_da_master
fonte