Pourquoi jQuery ou une méthode DOM telle que getElementById ne trouve-t-elle pas l'élément?

483

Quelles sont les raisons possibles document.getElementById, $("#id")ou toute autre méthode DOM / sélecteur jQuery ne trouvant pas les éléments?

Exemples de problèmes:

  • jQuery échoue silencieusement à lier un gestionnaire d'événements
  • jQuery méthodes "getter" ( .val(), .html(), .text()) retourundefined
  • Une méthode DOM standard renvoyant nullentraînant l'une des erreurs suivantes:

TypeError non capturé: impossible de définir la propriété «...» de null TypeError non détecté: impossible de lire la propriété «...» de null

Les formes les plus courantes sont:

TypeError non capturé: impossible de définir la propriété «onclick» de null

TypeError non capturé: impossible de lire la propriété 'addEventListener' de null

TypeError non capturé: impossible de lire la propriété 'style' de null

Felix Kling
la source
32
De nombreuses questions sont posées sur la raison pour laquelle un certain élément DOM n'est pas trouvé et cela est souvent dû au fait que le code JavaScript est placé avant l'élément DOM. Il s'agit d'une réponse canonique à ce type de questions. C'est un wiki communautaire, alors n'hésitez pas à l'améliorer .
Felix Kling

Réponses:

481

L'élément que vous tentiez de trouver n'était pas dans le DOM lors de l' exécution de votre script.

La position de votre script DOM-dépendant peut avoir un effet profond sur son comportement. Les navigateurs analysent les documents HTML de haut en bas. Des éléments sont ajoutés au DOM et les scripts sont (généralement) exécutés lorsqu'ils sont rencontrés. Cela signifie que l'ordre est important. En règle générale, les scripts ne peuvent pas trouver des éléments qui apparaissent plus tard dans le balisage car ces éléments doivent encore être ajoutés au DOM.

Considérez le balisage suivant; le script n ° 1 ne parvient pas à trouver le <div>script n ° 2 réussit:

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

Alors, que devrais-tu faire? Vous avez quelques options:


Option 1: déplacer votre script

Déplacez votre script plus bas dans la page, juste avant la balise de fermeture. Organisé de cette manière, le reste du document est analysé avant l'exécution de votre 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 -->

Remarque: Le placement de scripts en bas est généralement considéré comme une meilleure pratique .


Option 2: jQuery ready()

Reportez votre script jusqu'à ce que le DOM soit complètement analysé, en utilisant :$(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>

Remarque: Vous pouvez simplement vous lier à DOMContentLoadedou mais chacun a ses mises en garde. jQuery propose une solution hybride.window.onloadready()


Option 3: délégation d'événement

Les événements délégués ont l'avantage de pouvoir traiter les événements des éléments descendants qui sont ajoutés au document ultérieurement.

Lorsqu'un élément déclenche un événement (à condition qu'il s'agisse d'un événement bouillonnant et que rien n'arrête sa propagation), chaque parent de l'ascendance de cet élément reçoit également l'événement. Cela nous permet d'attacher un gestionnaire à un élément existant et d'exemples d'événements au fur et à mesure qu'ils remontent de ses descendants ... même ceux ajoutés après que le gestionnaire est attaché. Tout ce que nous avons à faire est de vérifier l'événement pour voir s'il a été déclenché par l'élément souhaité et, si c'est le cas, d'exécuter notre code.

jQuery's on()exécute cette logique pour nous. Nous fournissons simplement un nom d'événement, un sélecteur pour le descendant souhaité et un gestionnaire d'événements:

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

Remarque: En règle générale, ce modèle est réservé aux éléments qui n'existaient pas au moment du chargement ou pour éviter d'attacher une grande quantité de gestionnaires. Il convient également de noter que même si j'ai attaché un gestionnaire à document(à des fins de démonstration), vous devez sélectionner l'ancêtre fiable le plus proche.


Option 4: l' deferattribut

Utilisez l' deferattribut de <script>.

[ defer, un attribut booléen,] est défini pour indiquer à un navigateur que le script est destiné à être exécuté après l'analyse du document, mais avant son lancement DOMContentLoaded.

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

Pour référence, voici le code de ce script externe :

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

Remarque: l' deferattribut semble certainement être une solution miracle, mais il est important d'être conscient des mises en garde ...
1. deferne peut être utilisé que pour des scripts externes, c'est-à-dire ceux qui ont un srcattribut.
2. soyez conscient de la prise en charge du navigateur , c'est-à-dire: implémentation de buggy dans IE <10

canon
la source
Nous sommes maintenant en 2020 - "Placer les scripts en bas" est-il "toujours" considéré comme une meilleure pratique "? J'ai (aujourd'hui) mis toutes mes ressources dans <head>et utiliser des deferscripts (je n'ai pas à supporter de mauvais vieux navigateurs incompatibles)
Stephen P
Je suis un grand partisan de passer aux navigateurs modernes. Cela dit, cette pratique ne me coûte rien et elle fonctionne partout. D'un autre côté, les deux deferet asyncont un support assez large, allant même jusqu'à des versions de navigateur beaucoup plus anciennes. Ils ont l'avantage supplémentaire de signaler clairement et explicitement le comportement souhaité. N'oubliez pas que ces attributs ne fonctionnent que pour les scripts spécifiant un srcattribut, c'est-à-dire les scripts externes. Pesez les avantages. Peut-être utiliser les deux? Ton appel.
canon
146

Bref et simple: car les éléments que vous recherchez n'existent pas (encore) dans le document.


Pour le reste de cette réponse, je vais utiliser getElementByIdcomme exemple, mais la même chose s'applique à getElementsByTagName, querySelectoret à toute autre méthode DOM qui sélectionne des éléments.

Raisons possibles

Il existe deux raisons pour lesquelles un élément peut ne pas exister:

  1. Un élément avec l'ID transmis n'existe vraiment pas dans le document. Vous devez vérifier que l'ID que vous transmettez getElementByIdcorrespond vraiment à l'ID d'un élément existant dans le code HTML (généré) et que vous n'avez pas mal orthographié l'ID (les ID sont sensibles à la casse !).

    Par ailleurs, dans la majorité des navigateurs contemporains , qui implémentent querySelector()et querySelectorAll()méthodes, la notation de style CSS est utilisée pour récupérer un élément par son id, par exemple:, document.querySelector('#elementID')par opposition à la méthode par laquelle un élément est récupéré par son idsous document.getElementById('elementID'); dans le premier le #caractère est essentiel, dans le second il conduirait à ce que l'élément ne soit pas récupéré.

  2. L'élément n'existe pas au moment où vous appelez getElementById.

Ce dernier cas est assez courant. Les navigateurs analysent et traitent le HTML de haut en bas. Cela signifie que tout appel à un élément DOM qui se produit avant que cet élément DOM n'apparaisse dans le HTML, échoue.

Prenons l'exemple suivant:

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

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

Le divapparaît après le script. Au moment où le script est exécuté, l'élément n'existe pas encore et getElementByIdreviendra null.

jQuery

La même chose s'applique à tous les sélecteurs avec jQuery. jQuery ne trouvera pas d'éléments si vous avez mal orthographié votre sélecteur ou si vous essayez de les sélectionner avant qu'ils n'existent réellement .

Une torsion supplémentaire se produit lorsque jQuery est introuvable car vous avez chargé le script sans protocole et exécutez à partir du système de fichiers:

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

cette syntaxe est utilisée pour permettre au script de se charger via HTTPS sur une page avec le protocole https: // et de charger la version HTTP sur une page avec le protocole http: //

Il a le malheureux effet secondaire de tenter et de ne pas charger file://somecdn.somewhere.com...


Solutions

Avant d'appeler getElementById(ou toute méthode DOM d'ailleurs), assurez-vous que les éléments auxquels vous souhaitez accéder existent, c'est-à-dire que le DOM est chargé.

Cela peut être assuré en plaçant simplement votre JavaScript après l'élément DOM correspondant

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

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

dans ce cas, vous pouvez également mettre le code juste avant la balise de fermeture du corps ( </body>) (tous les éléments DOM seront disponibles au moment de l'exécution du script).

D'autres solutions incluent l'écoute des événements load [MDN] ou DOMContentLoaded [MDN] . Dans ces cas, peu importe où dans le document vous placez le code JavaScript, il vous suffit de vous rappeler de mettre tout le code de traitement DOM dans les gestionnaires d'événements.

Exemple:

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

// or

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

Veuillez consulter les articles sur quirksmode.org pour plus d'informations sur la gestion des événements et les différences de navigateur.

jQuery

Assurez-vous d'abord que jQuery est correctement chargé. Utilisez les outils de développement du navigateur pour savoir si le fichier jQuery a été trouvé et corrigez l'URL dans le cas contraire (par exemple, ajoutez le schéma http:ou https:au début, ajustez le chemin, etc.)

L'écoute des événements load/ DOMContentLoadedest exactement ce que jQuery fait avec .ready() [docs] . Tout votre code jQuery qui affecte l'élément DOM doit se trouver dans ce gestionnaire d'événements.

En fait, le didacticiel jQuery indique explicitement:

Comme presque tout ce que nous faisons lors de l'utilisation de jQuery lit ou manipule le modèle d'objet de document (DOM), nous devons nous assurer que nous commençons à ajouter des événements, etc. dès que le DOM est prêt.

Pour ce faire, nous enregistrons un événement prêt pour le document.

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

Alternativement, vous pouvez également utiliser la syntaxe abrégée:

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

Les deux sont équivalents.

Felix Kling
la source
1
Vaut-il la peine de modifier la dernière partie de l' readyévénement pour refléter la syntaxe préférée "plus récente", ou est-il préférable de la laisser telle quelle pour qu'elle fonctionne avec les anciennes versions?
Tieson T.
1
Pour jQuery, il vaut la peine d' ajouter également à titre subsidiaire $(window).load()(et peut - être des solutions de rechange syntaxiques à la $(document).ready(), $(function(){})il semble lié, mais il? Sent un peu tangent au point que vous faites.
David dit Réintégrer Monica
@David: Bon point avec .load. En ce qui concerne les alternatives de syntaxe, elles pourraient être consultées dans la documentation, mais comme les réponses devraient être autonomes, il pourrait être utile de les ajouter. Là encore, je suis presque sûr que ce bit spécifique sur jQuery a déjà été répondu et est probablement couvert dans une autre réponse et un lien vers celui-ci pourrait suffire.
Felix Kling du
1
@David: Je dois réviser: Apparemment, il .loadest obsolète: api.jquery.com/load-event . Je ne sais pas si c'est pour les images uniquement ou windowaussi.
Felix Kling
1
@David: Pas de soucis :) Même si vous n'êtes pas sûr, c'est bien que vous l'ayez mentionné. J'ajouterai probablement quelques simples extraits de code pour montrer l'utilisation. Merci pour votre contribution!
Felix Kling
15

Raisons pour lesquelles les sélecteurs basés sur l'ID ne fonctionnent pas

  1. L'élément / DOM avec l'ID spécifié n'existe pas encore.
  2. L'élément existe, mais il n'est pas enregistré dans DOM [dans le cas de nœuds HTML ajoutés dynamiquement à partir des réponses Ajax].
  3. Plusieurs éléments portant le même identifiant sont présents, ce qui provoque un conflit.

Solutions

  1. Essayez d'accéder à l'élément après sa déclaration ou utilisez des trucs comme $(document).ready();

  2. Pour les éléments provenant des réponses Ajax, utilisez la .bind()méthode de jQuery. Les anciennes versions de jQuery avaient .live()la même chose.

  3. Utilisez des outils [par exemple, le plugin webdeveloper pour les navigateurs] pour trouver les identifiants en double et les supprimer.

sumit
la source
13

Comme l'a souligné @FelixKling, le scénario le plus probable est que les nœuds que vous recherchez n'existent pas (encore).

Cependant, les pratiques de développement modernes peuvent souvent manipuler des éléments de document en dehors de l'arborescence de documents soit avec DocumentFragments, soit simplement en détachant / rattachant directement les éléments actuels. Ces techniques peuvent être utilisées dans le cadre de modèles JavaScript ou pour éviter des opérations de repeinture / refusion excessives pendant que les éléments en question sont fortement modifiés.

De même, la nouvelle fonctionnalité "Shadow DOM" déployée sur les navigateurs modernes permet aux éléments de faire partie du document, mais pas d'interroger par document.getElementById et toutes ses méthodes apparentées (querySelector, etc.). Cette opération est effectuée pour encapsuler les fonctionnalités et les masquer spécifiquement.

Encore une fois, cependant, il est très probable que l'élément que vous recherchez ne figure tout simplement pas (encore) dans le document, et vous devriez faire comme le suggère Felix. Cependant, vous devez également savoir que ce n'est pas de plus en plus la seule raison pour laquelle un élément peut être introuvable (temporairement ou définitivement).

Nathan Bubna
la source
13

Si l'élément auquel vous essayez d'accéder se trouve à l'intérieur d'un iframeet que vous essayez d'y accéder en dehors du contexte de iframecela, cela entraînera également son échec.

Si vous souhaitez obtenir un élément dans un iframe, vous pouvez découvrir comment ici .

George Mulligan
la source
7

Si l'ordre d'exécution du script n'est pas le problème, une autre cause possible du problème est que l'élément n'est pas sélectionné correctement:

  • getElementByIdrequiert que la chaîne passée soit l'ID mot pour mot , et rien d'autre. Si vous préfixez la chaîne passée avec un #et que l'ID ne commence pas par un #, rien ne sera sélectionné:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
  • De même, pour getElementsByClassName, ne préfixez pas la chaîne passée avec un .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • Avec querySelector, querySelectorAll et jQuery, pour faire correspondre un élément avec un nom de classe particulier, placez un .directement avant la classe. De même, pour faire correspondre un élément avec un ID particulier, mettez un #directement avant l'ID:

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

    Les règles ici sont, dans la plupart des cas, identiques à celles des sélecteurs CSS, et peuvent être vues en détail ici .

  • Pour faire correspondre un élément qui a deux attributs ou plus (comme deux noms de classe, ou un nom de classe et un data-attribut), placez les sélecteurs pour chaque attribut côte à côte dans la chaîne de sélection, sans espace les séparant (car un espace indique le sélecteur descendant ). Par exemple, pour sélectionner:

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

    utilisez la chaîne de requête .foo.bar. Pour sélectionner

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

    utilisez la chaîne de requête .foo[data-bar="someData"]. Pour sélectionner ce qui <span>suit:

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

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

  • La capitalisation et l'orthographe comptent pour tout ce qui précède. Si la capitalisation est différente, ou l'orthographe est différente, l'élément ne sera pas sélectionné:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • Vous devez également vous assurer que les méthodes sont correctement capitalisées et orthographiées. Utilisez l'un des:

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

    Toute autre orthographe ou capitalisation ne fonctionnera pas. Par exemple, document.getElementByClassNamelancera une erreur.

  • Assurez-vous de passer une chaîne à ces méthodes de sélection. Si vous passez quelque chose qui n'est pas une chaîne querySelector, getElementByIdetc., presque certainement ne fonctionnera pas.

CertainPerformance
la source
0

Quand j'ai essayé votre code, cela a fonctionné.

La seule raison pour laquelle votre événement ne fonctionne pas peut être que votre DOM n'était pas prêt et que votre bouton avec l'ID "event-btn" n'était pas encore prêt. Et votre javascript a été exécuté et a tenté de lier l'événement avec cet élément.

Avant d'utiliser l'élément DOM pour la liaison, cet élément doit être prêt. Il existe de nombreuses options pour ce faire.

Option 1: vous pouvez déplacer votre code de liaison d'événement dans un événement prêt pour le document. Comme:

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

Option 2: vous pouvez utiliser l'événement d'expiration, de sorte que la liaison soit retardée de quelques secondes. comme:

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

Option3: déplacez votre inclusion javascript au bas de votre page.

J'espère que ceci vous aide.

Jatinder Kumar
la source
@ Willdomybest18, veuillez vérifier cette réponse
Jatinder Kumar
-1

le problème est que l'élément dom 'speclist' n'est pas créé au moment où le code javascript est exécuté. J'ai donc mis le code javascript dans une fonction et j'ai appelé cette fonction lors de l'événement onload du corps.

function do_this_first(){
   //appending code
}

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

Ma solution était de ne pas utiliser l'identifiant d'un élément d'ancrage: <a id='star_wars'>Place to jump to</a>. Apparemment, le blazor et les autres cadres de spa ont des problèmes pour sauter aux ancres sur la même page. Pour me déplacer, je devais utiliser document.getElementById('star_wars'). Cependant , cela ne fonctionnait pas jusqu'à ce que je mets l'id dans un élément de paragraphe à la place: <p id='star_wars'>Some paragraph<p>.

Exemple utilisant 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
la source