¿Por qué jQuery o un método DOM como getElementById no encuentra el elemento?

483

¿Cuáles son las posibles razones para document.getElementById, $("#id")o cualquier otro método DOM / jQuery selector para no encontrar los elementos?

Los problemas de ejemplo incluyen:

  • jQuery silenciosamente no puede vincular un controlador de eventos
  • jQuery métodos "getter" ( .val(), .html(), .text()) devolverundefined
  • Un método DOM estándar que devuelve el nullresultado de cualquiera de varios errores:

TypeError no capturado: no se puede establecer la propiedad '...' de nulo TypeError no capturado: no se puede leer la propiedad '...' de nulo

Las formas más comunes son:

TypeError no capturado: no se puede establecer la propiedad 'onclick' de nulo

TypeError no capturado: no se puede leer la propiedad 'addEventListener' de null

TypeError no capturado: no se puede leer la propiedad 'estilo' de nulo

Felix Kling
fuente
32
Se hacen muchas preguntas sobre por qué no se encuentra un determinado elemento DOM y la razón es a menudo porque el código JavaScript se coloca antes del elemento DOM. Esta pretende ser una respuesta canónica para este tipo de preguntas. Es una wiki comunitaria, así que siéntete libre de mejorarla .
Felix Kling

Respuestas:

481

El elemento que intentaba encontrar no estaba en el DOM cuando se ejecutó su script.

La posición de su script dependiente de DOM puede tener un profundo efecto sobre su comportamiento. Los navegadores analizan los documentos HTML de arriba a abajo. Los elementos se agregan al DOM y los scripts se ejecutan (generalmente) a medida que se encuentran. Esto significa que el orden importa. Por lo general, los scripts no pueden encontrar elementos que aparecen más adelante en el marcado porque esos elementos aún no se han agregado al DOM.

Considere el siguiente marcado; el script # 1 no puede encontrar el <div>script while # 2 tiene éxito:

<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>

¿Entonces, qué debería hacer? Tienes algunas opciones:


Opción 1: mueve tu script

Mueva su script más abajo en la página, justo antes de la etiqueta de cierre del cuerpo. Organizado de esta manera, el resto del documento se analiza antes de ejecutar el 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: colocar guiones en la parte inferior generalmente se considera una práctica recomendada .


Opción 2: jQuery's ready()

Aplaza tu secuencia de comandos hasta que el DOM se haya analizado por completo, 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: Usted puede simplemente enlazar con DOMContentLoadedo pero cada uno tiene sus advertencias. jQuery's ofrece una solución híbrida.window.onloadready()


Opción 3: Delegación de eventos

Los eventos delegados tienen la ventaja de que pueden procesar eventos de elementos descendientes que se agregan al documento más adelante.

Cuando un elemento genera un evento (siempre que sea un evento burbujeante y nada detenga su propagación), cada padre en la ascendencia de ese elemento también recibe el evento. Eso nos permite adjuntar un controlador a un elemento existente y eventos de muestra a medida que surgen de sus descendientes ... incluso los que se agregan después de adjuntar el controlador. Todo lo que tenemos que hacer es verificar el evento para ver si fue generado por el elemento deseado y, de ser así, ejecutar nuestro código.

jQuery's on()realiza esa lógica por nosotros. Simplemente proporcionamos un nombre de evento, un selector para el descendiente deseado y un controlador 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, este patrón está reservado para elementos que no existían en el momento de la carga o para evitar asociar una gran cantidad de controladores. También vale la pena señalar que si bien he adjuntado un controlador a document(con fines demostrativos), debe seleccionar el antepasado confiable más cercano.


Opción 4: el deferatributo

Usa el deferatributo de <script>.

[ defer, un atributo booleano,] está configurado para indicar a un navegador que el script debe ejecutarse después de analizar el documento, pero antes de dispararlo DOMContentLoaded.

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

Como referencia, aquí está el código de ese script externo :

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

Nota: El deferatributo ciertamente parece una bala mágica, pero es importante tener en cuenta las advertencias ...
1. defersolo se puede usar para scripts externos, es decir, aquellos que tienen un srcatributo.
2. Tenga en cuenta la compatibilidad del navegador , es decir: implementación defectuosa en IE <10

canon
fuente
Ahora es 2020: ¿"Colocar guiones en la parte inferior" todavía "se considera una mejor práctica"? Yo (hoy en día) pongo todos mis recursos en los scripts <head>y los uso defer(no tengo que admitir navegadores incompatibles antiguos)
Stephen P
Soy un gran defensor de pasar a los navegadores modernos. Dicho esto, esta práctica realmente no me cuesta nada y simplemente funciona en todas partes. Por otro lado, ambos defery asynctienen un soporte bastante amplio, incluso llegando a algunas versiones de navegador mucho más antiguas. Tienen la ventaja adicional de señalar de manera clara y explícita el comportamiento deseado. Solo recuerde que estos atributos solo funcionan para scripts que especifican un srcatributo, es decir: scripts externos. Sopesa los beneficios. Tal vez use ambos? Tu llamada.
canon
146

Breve y simple: porque los elementos que está buscando no existen en el documento (todavía).


Para el resto de esta respuesta voy a utilizar getElementByIdcomo ejemplo, pero lo mismo se aplica a getElementsByTagName, querySelectory cualquier otro método DOM que los elementos seleccione.

Posibles razones

Hay dos razones por las cuales un elemento podría no existir:

  1. Un elemento con la ID pasada realmente no existe en el documento. Debe verificar que la ID que pasa getElementByIdrealmente coincida con la ID de un elemento existente en el HTML (generado) y que no haya escrito mal la ID (¡las ID distinguen entre mayúsculas y minúsculas !).

    Por cierto, en la mayoría de los navegadores contemporáneos , que implementan querySelector()y querySelectorAll()métodos, la notación de estilo CSS se utiliza para recuperar un elemento por su id, por ejemplo:, a document.querySelector('#elementID')diferencia del método por el cual un elemento se recupera por iddebajo document.getElementById('elementID'); en el primero el #personaje es esencial, en el segundo llevaría a que el elemento no se recupere.

  2. El elemento no existe en el momento que llamas getElementById.

El último caso es bastante común. Los navegadores analizan y procesan el HTML de arriba a abajo. Eso significa que cualquier llamada a un elemento DOM que ocurra antes de que ese elemento DOM aparezca en el HTML, fallará.

Considere el siguiente ejemplo:

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

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

El divaparece después de la script. En el momento en que se ejecuta la secuencia de comandos, el elemento no existe todavía y getElementByIdvolverá null.

jQuery

Lo mismo se aplica a todos los selectores con jQuery. jQuery no encontrará elementos si ha escrito mal su selector o está intentando seleccionarlos antes de que realmente existan .

Un giro adicional es cuando no se encuentra jQuery porque ha cargado el script sin protocolo y se está ejecutando desde el sistema de archivos:

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

Esta sintaxis se utiliza para permitir que el script se cargue a través de HTTPS en una página con el protocolo https: // y para cargar la versión HTTP en una página con el protocolo http: //

Tiene el desafortunado efecto secundario de intentar y no cargar file://somecdn.somewhere.com...


Soluciones

Antes de hacer una llamada a getElementById(o cualquier método DOM para el caso), asegúrese de que los elementos a los que desea acceder existan, es decir, que el DOM esté cargado.

Esto se puede garantizar simplemente colocando su JavaScript después del elemento DOM correspondiente

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

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

en cuyo caso también puede poner el código justo antes de la etiqueta del cuerpo de cierre ( </body>) (todos los elementos DOM estarán disponibles en el momento en que se ejecute el script).

Otras soluciones incluyen escuchar los eventos load [MDN] o DOMContentLoaded [MDN] . En estos casos, no importa en qué parte del documento coloque el código JavaScript, solo debe recordar colocar todo el código de procesamiento DOM en los controladores de eventos.

Ejemplo:

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

// or

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

Consulte los artículos en quirksmode.org para obtener más información sobre el manejo de eventos y las diferencias del navegador.

jQuery

Primero asegúrese de que jQuery esté cargado correctamente. Utilice las herramientas de desarrollo del navegador para averiguar si se encontró el archivo jQuery y corrija la URL si no fue así (por ejemplo, agregue el esquema http:o https:al principio, ajuste la ruta, etc.)

Escuchar los eventos load/ DOMContentLoadedes exactamente lo que está haciendo jQuery con .ready() [docs] . Todo el código jQuery que afecta al elemento DOM debe estar dentro de ese controlador de eventos.

De hecho, el tutorial jQuery declara explícitamente:

Como casi todo lo que hacemos cuando usamos jQuery lee o manipula el modelo de objetos del documento (DOM), debemos asegurarnos de comenzar a agregar eventos, etc., tan pronto como el DOM esté listo.

Para hacer esto, registramos un evento listo para el documento.

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

Alternativamente, también puede usar la sintaxis abreviada:

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

Ambos son equivalentes.

Felix Kling
fuente
1
¿Valdría la pena modificar el último fragmento sobre el readyevento para reflejar la sintaxis preferida "más reciente", o es mejor dejarlo como está para que funcione en versiones anteriores?
Tieson T.
1
Para jQuery, ¿merece la pena, además de darle en la alternativa $(window).load()(y posiblemente alternativas sintácticas al $(document).ready(), $(function(){})Se parece estar relacionado, pero? Se siente ligeramente tangencial al punto que está haciendo.
David dice de reactivación de Mónica
@David: Buen punto con .load. Con respecto a las alternativas de sintaxis, podrían buscarse en la documentación, pero dado que las respuestas deben ser independientes, en realidad valdría la pena agregarlas. Por otra parte, estoy bastante seguro de que este bit específico sobre jQuery ya fue respondido y probablemente esté cubierto en otras respuestas y puede ser suficiente vincularlo.
Felix Kling
1
@David: tengo que revisar: aparentemente .loadestá en desuso: api.jquery.com/load-event . No sé si es solo para imágenes o windowtambién.
Felix Kling
1
@David: No te preocupes :) Incluso si no estás seguro, es bueno que lo hayas mencionado. Probablemente agregaré solo algunos fragmentos de código simples para mostrar el uso. ¡Gracias por tu contribución!
Felix Kling
15

Razones por las cuales los selectores basados ​​en id no funcionan

  1. El elemento / DOM con ID especificado aún no existe.
  2. El elemento existe, pero no está registrado en DOM [en el caso de nodos HTML agregados dinámicamente a partir de respuestas Ajax].
  3. Más de un elemento con la misma identificación está presente, lo que está causando un conflicto.

Soluciones

  1. Intente acceder al elemento después de su declaración o, alternativamente, use cosas como $(document).ready();

  2. Para los elementos que provienen de las respuestas de Ajax, use el .bind()método de jQuery. Las versiones anteriores de jQuery tenían .live()lo mismo.

  3. Use herramientas [por ejemplo, plugin de desarrollador web para navegadores] para buscar identificadores duplicados y eliminarlos.

sumitir
fuente
13

Como señaló @FelixKling, el escenario más probable es que los nodos que está buscando no existen (todavía).

Sin embargo, las prácticas de desarrollo modernas a menudo pueden manipular elementos del documento fuera del árbol de documentos, ya sea con DocumentFragments o simplemente separando / volviendo a colocar los elementos actuales directamente. Dichas técnicas se pueden usar como parte de la plantilla de JavaScript o para evitar operaciones excesivas de repintado / reflujo mientras los elementos en cuestión se alteran mucho.

Del mismo modo, la nueva funcionalidad "Shadow DOM" que se implementa en los navegadores modernos permite que los elementos formen parte del documento, pero que no se puedan consultar por document.getElementById y todos sus métodos hermanos (querySelector, etc.). Esto se hace para encapsular la funcionalidad y ocultarla específicamente.

Sin embargo, de nuevo, es muy probable que el elemento que está buscando simplemente no esté (todavía) en el documento, y debe hacer lo que sugiere Felix. Sin embargo, también debe tener en cuenta que esa no es cada vez más la única razón por la que un elemento puede ser inaccesible (ya sea temporal o permanentemente).

Nathan Bubna
fuente
13

Si el elemento al que intenta acceder está dentro de iframee e intenta acceder fuera del contexto de iframeesto, esto también hará que falle.

Si desea obtener un elemento en un iframe, puede averiguar cómo hacerlo aquí .

George Mulligan
fuente
7

Si el problema no es el orden de ejecución del script, otra posible causa del problema es que el elemento no se está seleccionando correctamente:

  • getElementByIdrequiere que la cadena pasada sea el ID literal , y nada más. Si antepone la cadena pasada con a #, y la ID no comienza con a #, no se seleccionará nada:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
  • Del mismo modo, para getElementsByClassName, no prefijas la cadena pasada con un .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • Con querySelector, querySelectorAll y jQuery, para hacer coincidir un elemento con un nombre de clase particular, coloque un elemento .directamente antes de la clase. Del mismo modo, para hacer coincidir un elemento con un ID en particular, coloque un #directamente antes del ID:

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

    Las reglas aquí son, en la mayoría de los casos, idénticas a las de los selectores CSS, y se pueden ver en detalle aquí .

  • Para hacer coincidir un elemento que tiene dos o más atributos (como dos nombres de clase, o un nombre de clase y un data-atributo), coloque los selectores para cada atributo uno al lado del otro en la cadena de selección, sin un espacio que los separe (porque un espacio indica El selector descendiente ). Por ejemplo, para seleccionar:

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

    usa la cadena de consulta .foo.bar. Para seleccionar

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

    usa la cadena de consulta .foo[data-bar="someData"]. Para seleccionar lo <span>siguiente:

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

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

  • Las mayúsculas y la ortografía son importantes para todo lo anterior. Si la capitalización es diferente, o la ortografía es diferente, el elemento no se seleccionará:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • También debe asegurarse de que los métodos tengan las mayúsculas y la ortografía adecuadas. Use uno de:

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

    Cualquier otra ortografía o capitalización no funcionará. Por ejemplo, document.getElementByClassNamearrojará un error.

  • Asegúrese de pasar una cadena a estos métodos de selección. Si pasa algo que no es una cadena a querySelector, getElementById, etc, es casi seguro que no va a funcionar.

Cierto rendimiento
fuente
0

Cuando probé tu código, funcionó.

La única razón por la que su evento no funciona es que su DOM no estaba listo y su botón con la identificación "event-btn" aún no estaba listo. Y su javascript se ejecutó e intentó vincular el evento con ese elemento.

Antes de usar el elemento DOM para el enlace, ese elemento debe estar listo. Hay muchas opciones para hacer eso.

Opción 1: puede mover su código de enlace de evento dentro del evento de documento listo. Me gusta:

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

Opción 2: puede usar el evento de tiempo de espera, de modo que el enlace se retrase durante unos segundos. me gusta:

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

Opción 3: mueva su javascript incluido al final de su página.

Espero que esto te ayude.

Jatinder Kumar
fuente
@ Willdomybest18, compruebe esta respuesta
Jatinder Kumar
-1

El problema es que el elemento dom 'speclist' no se crea en el momento en que se ejecuta el código JavaScript. Así que puse el código javascript dentro de una función y llamé a esa función en el evento de carga del cuerpo.

function do_this_first(){
   //appending code
}

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

Mi solución fue la de no utilizar el id de un elemento de anclaje: <a id='star_wars'>Place to jump to</a>. Aparentemente, Blazor y otros marcos de spa tienen problemas para saltar a los anclajes en la misma página. Para evitar eso tuve que usar document.getElementById('star_wars'). Sin embargo esto no funcionó hasta que ponga el id en un elemento de párrafo en su lugar: <p id='star_wars'>Some paragraph<p>.

Ejemplo 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
fuente