javascript - De ce jQuery sau o metodă DOM precum getElementById nu găsesc elementul?


Care sunt posibilele motive pentru ca document.getElementById, $ ("#id") sau orice altă metodă DOM/selector jQuery să nu găsească elementele?

Problemele de exemplu includ:

  • jQuery nu reușește să lege un handler de eveniment în mod silențios
  • Metodele „getter” jQuery (.val(), .html(), .text()) returnează undefined
  • O metodă standard DOM care returnează null, rezultând în oricare dintre mai multe erori:

TypeError nepreluat: Nu se poate seta proprietatea ''...'' a valorii null
TypeError nepreluat: Nu se pot seta proprietățile valorii null (setând ''...'')
TypeError nepreluat: Nu se poate citi proprietatea ''...'' a valorii null
TypeError nepreluat: Nu se pot citi proprietățile valorii null (citind ''...'')

Cele mai comune forme sunt:

TypeError nepreluat: Nu se poate seta proprietatea 'onclick' a lui null
TypeError nepreluat: Nu se poate citi proprietatea 'addEventListener' a lui null
TypeError nepreluat: Nu se poate citi proprietatea 'style' a lui null

Răspuns #1

Elementul pe care încercai să îl găsești nu era în DOM când s-a rulat scriptul tău.

Poziția scriptului tău care depinde de DOM poate avea un efect profund asupra comportamentului său. Browserele parsează documentele HTML de sus în jos. Elementele sunt adăugate în DOM, iar scripturile sunt (implicit) executate pe măsură ce sunt întâlnite. Aceasta înseamnă că ordinea contează. În mod tipic, scripturile nu pot găsi elementele care apar mai târziu în markup pentru că acele elemente nu au fost încă adăugate în DOM.

Luați în considerare următorul markup; scriptul #1 nu reușește să găsească <div> în timp ce scriptul #2 reușește:

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

Deci, ce ar trebui să faci? Ai câteva opțiuni:

Opțiunea 1: Mută-ți scriptul

Având în vedere ceea ce am văzut în exemplul de mai sus, o soluție intuitivă ar putea fi pur și simplu să muți scriptul mai jos în marcaj, după elementele la care dorești să ai acces. De fapt, pentru mult timp, plasarea scripturilor la baza paginii a fost considerată o bună practică din mai multe motive. Organizate în acest mod, restul documentului ar fi analizat înainte de a executa scriptul tău:

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

Deși acest lucru are sens și este o opțiune solidă pentru browserele vechi, este limitat și există abordări mai flexibile și moderne disponibile.

Opțiunea 2: Atributul defer

Deși am spus că scripturile sunt "(implicit) executate pe măsură ce sunt întâlnite," browserele moderne îți permit să specifici un comportament diferit. Dacă legi un script extern, poți folosi atributul defer.

[defer, un atribut Boolean,] este setat pentru a indica unui browser că scriptul este destinat să fie executat după ce documentul a fost analizat, dar înainte de declanșarea DOMContentLoaded.

Aceasta înseamnă că poți plasa un script etichetat cu defer oriunde, chiar și în <head>, și ar trebui să aibă acces la DOM-ul complet realizat.

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

Doar ține minte...

  1. defer poate fi folosit doar pentru scripturi externe, adică pentru cele care au un atribut src.
  2. fii conștient de suportul browserului, adică: implementare cu erori în IE < 10

Opțiunea 3: Module

În funcție de cerințele dvs., este posibil să puteți utiliza module JavaScript. Printre alte diferențe importante față de scripturile standard (notate aici), modulele sunt întârziate automat și nu sunt limitate la surse externe.

Setați type al scriptului dvs. la module, de exemplu:

<script type="module">
  document.getElementById("test").addEventListener("click", function(e) {
    console.log("clicked: ", this);
  });
</script>
<button id="test">click me</button>

Opțiunea 4: Amână cu gestionarea evenimentelor

Adaugă un ascultător pentru un eveniment care se declanșează după ce documentul tău a fost analizat.

evenimentul DOMContentLoaded

DOMContentLoaded se declanșează după ce DOM-ul a fost complet construit din analiza inițială, fără a aștepta ca lucruri precum foile de stil sau imaginile să se încarce.

<script>
  document.addEventListener("DOMContentLoaded", function(e){
    document.getElementById("test").addEventListener("click", function(e) {
      console.log("clicked:", this);
    });
  });
</script>
<button id="test">click me</button>

Fereastră: eveniment de încărcare

Evenimentul load se declanșează după DOMContentLoaded și după ce resurse suplimentare, precum foile de stil și imaginile, au fost încărcate. Din acest motiv, se declanșează mai târziu decât ne-am dori pentru scopurile noastre. Totuși, dacă iei în considerare browsere mai vechi precum IE8, suportul este aproape universal. Desigur, este posibil să dorești un polyfill pentru addEventListener().

<script>
  window.addEventListener("load", function(e){
    document.getElementById("test").addEventListener("click", function(e) {
      console.log("clicked:", this);
    });
  });
</script>
<button id="test">click me</button>

functie ready() din jQuery

DOMContentLoaded și window:load au fiecare limitările lor. ready() din jQuery oferă o soluție hibridă, folosind DOMContentLoaded atunci când este posibil, trecând la window:load când este necesar și declanșând callback-ul imediat dacă DOM-ul este deja complet.

Poți transmite handler-ul tău gata direct către jQuery ca $ (handler), de exemplu:

<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script>
  $(function() {
    $("#test").click(function() {
      console.log("clicked:", this);
    });
  });
</script>
<button id="test">click me</button>

Opțiunea 5: Delegarea Evenimentelor

Deleagă gestionarea evenimentului către un strămoș al elementului țintă.

Când un element declanșează un eveniment (cu condiția ca acesta să fie un eveniment care face propagare și nimic să nu-i oprească propagarea), fiecare părinte din arborele său de ascendență, până la window, primește, de asemenea, evenimentul. Asta ne permite să atașăm un handler unui element existent și să preluăm evenimente pe măsură ce acestea urcă din descendentele sale... chiar și din descendentele adăugate după ce handler-ul a fost atașat. Tot ce trebuie să facem este să verificăm evenimentul pentru a vedea dacă a fost declanșat de elementul dorit și, dacă da, să rulăm codul nostru.

De obicei, acest model este rezervat pentru elemente care nu există la momentul încărcării sau pentru a evita atașarea unui număr mare de handleri duplicat. Pentru eficiență, selectați cel mai apropiat strămoș de încredere al elementului țintă în loc să-l atașați la document. Dacă este cazul, nu uitați de Event:stopPropagation() pentru a preveni propagarea/prelucrarea ulterioară.

JavaScript nativ

<div id="ancestor"><!-- nearest ancestor available to our script -->
  <script>
    document.getElementById("ancestor").addEventListener("click", function(e) {
      if (e.target.id === "descendant") {
        e.stopPropagation(); // stop bubbling
        console.log("clicked:", e.target);
      }
    });
  </script>
  <button id="descendant">click me</button>
</div>

on() din jQuery

jQuery face această funcționalitate disponibilă prin on(). Având un nume de eveniment, un selector pentru descendentul dorit și un handler pentru eveniment, va rezolva manipularea delegată a evenimentului și va gestiona contextul tău this:

<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<div id="ancestor"><!-- nearest ancestor available to our script -->
  <script>
    $("#ancestor").on("click", "#descendant", function(e) {
      e.stopPropagation(); // stop bubbling
      console.log("clicked:", this);
    });
  </script>
  <button id="descendant">click me</button>
</div>

Răspunsul #2

Scurt și simplu: Pentru că elementele pe care le cauți nu există (încă) în document.


Pentru restul acestui răspuns voi folosi getElementById pentru exemple, dar același lucru se aplică și pentru getElementsByTagName, querySelector și orice altă metodă DOM care selectează elemente.

Motive posibile

Există trei motive pentru care un element s-ar putea să nu existe:

  1. Un element cu ID-ul transmis chiar nu există în document. Ar trebui să verificați din nou dacă ID-ul pe care îl transmiteți la getElementById corespunde cu adevărat unui ID al unui element existent în HTML-ul (generat) și să vă asigurați că nu ați greșit scrierea a ID-ului (ID-urile sunt sensibile la majuscule și minuscule!).

    Dacă folosești getElementById, asigură-te că dai doar ID-ul elementului (de ex., document.getElementById("the-id")). Dacă folosești o metodă care acceptă un selector CSS (cum ar fi querySelector), asigură-te că incluzi # înaintea ID-ului pentru a indica că cauți un ID (de ex., document.querySelector("#the-id")). Nu trebuie să folosești # cu getElementById, și trebuie să-l folosești cu querySelector și metode similare. De asemenea, rețineți că dacă ID-ul conține caractere care nu sunt valide în identificatorii CSS (cum ar fi un .; atributele id care conțin caractere . sunt o practică proastă, dar valide), trebuie să le scăpați atunci când folosiți querySelector (document.querySelector("#the\\.id")), dar nu și când folosiți getElementById (document.getElementById("the.id")).

  2. Elementul nu există în momentul în care apelezi getElementById.

  3. Elementul nu se află în documentul pe care îl interogați chiar dacă îl puteți vedea pe pagină, deoarece se află într-un iframe (care este propriul său document). Elementele din iframe nu sunt căutate atunci când căutați în documentul care le conține.

Dacă problema este motivul 3 (este într-un iframe sau ceva similar), trebuie să te uiți prin documentul din iframe, nu prin documentul părinte, poate prin obținerea elementului iframe și folosind proprietatea sa contentDocument pentru a accesa documentul său (numai același origine). Restul acestui răspuns abordează primele două motive.

Al doilea motiv — nu este acolo încă — este destul de comun. Browserele analizează și procesează HTML-ul de sus în jos. Asta înseamnă că orice apel către un element DOM care are loc înainte ca acel element DOM să apară în HTML, va eșua.

Luați în considerare următorul exemplu:

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

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

div apare după script. În momentul în care scriptul este executat, elementul nu există încă și getElementById va returna null.

jQuery

Același lucru se aplică tuturor selectorilor cu jQuery. jQuery nu va găsi elementele dacă ați scris greșit selectorul sau încercați să le selectați înainte ca acestea să existe cu adevărat.

O complicație suplimentară apare atunci când jQuery nu este găsit deoarece ai încărcat scriptul fără protocol și rulezi din sistemul de fișiere:

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

această sintaxă este folosită pentru a permite scriptului să se încarce prin HTTPS pe o pagină cu protocol https:// și pentru a încărca versiunea HTTP pe o pagină cu protocol http://

Are efectul secundar nefericit de a încerca și a eșua să încarce file://somecdn.somewhere.com...


Soluții

Înainte de a face un apel la getElementById (sau orice metodă DOM de altfel), asigură-te că elementele la care vrei să ai acces există, adică DOM-ul este încărcat.

Acest lucru poate fi asigurat pur și simplu plasând JavaScript-ul tău după elementul DOM corespunzător

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

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

în acest caz poți de asemenea să pui codul chiar înainte de eticheta de închidere a corpului (</body>) (toate elementele DOM vor fi disponibile în momentul în care scriptul este executat).

Alte soluții includ ascultarea evenimentelor load [MDN] sau DOMContentLoaded [MDN]. În aceste cazuri, nu contează unde în document plasați codul JavaScript, trebuie doar să vă amintiți să puneți tot codul de procesare DOM în handler-ele de evenimente.

Exemplu :

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

// or

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

Vă rugăm să consultați articolele de pe quirksmode.org pentru mai multe informații privind gestionarea evenimentelor și diferențele dintre browsere.

jQuery

Mai întâi asigură-te că jQuery este încărcat corect. Folosește instrumentele pentru dezvoltatori ale browserului pentru a afla dacă fișierul jQuery a fost găsit și corectează URL-ul dacă nu a fost (de exemplu, adaugă schema http: sau https: la început, ajustează calea etc.)

Ascultarea evenimentelor load/DOMContentLoaded este exact ceea ce face jQuery cu .ready() [docs]. Tot codul tău jQuery care afectează elementele DOM ar trebui să fie în interiorul acelui handler de eveniment.

De fapt, tutorialul jQuery afirmă în mod explicit:

Deoarece aproape tot ce facem atunci când folosim jQuery citește sau manipulează modelul obiectului document (DOM), trebuie să ne asigurăm că începem să adăugăm evenimente etc. imediat ce DOM-ul este gata.

Pentru a face acest lucru, înregistrăm un eveniment ready pentru document.

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

Alternativ, poți folosi și sintaxa prescurtată:

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

Ambele sunt echivalente.

Răspunsul #3

Motive pentru care selectoarele bazate pe id nu funcționează

  1. Elementul/DOM-ul cu id-ul specificat nu există încă.
  2. Elementul există, dar nu este înregistrat în DOM [în cazul nodurilor HTML adăugate dinamic din răspunsurile Ajax].
  3. Mai mult de un element cu același id este prezent, ceea ce cauzează un conflict.

Soluții

  1. Încearcă să accesezi elementul după declarația sa sau, alternativ, folosește chestii precum $ (document).ready();

  2. Pentru elementele care provin din răspunsuri Ajax, folosiți metoda .bind() a jQuery. Versiunile mai vechi de jQuery aveau .live() pentru același lucru.

  3. Folosește unelte [de exemplu, pluginul webdeveloper pentru browsere] pentru a găsi ID-uri duplicate și a le elimina.

Răspunsul #4

Dacă elementul la care încercați să accesați se află într-un iframe și încercați să accesați în afara contextului iframe, acest lucru va cauza, de asemenea, eșecul.

Dacă vrei să obții un element dintr-un iframe, poți afla cum aici.

Răspunsul #5

Așa cum a subliniat @FelixKling, scenariul cel mai probabil este că nodurile pe care le cauți nu există (încă).

Totuși, practicile moderne de dezvoltare pot adesea să manipuleze elementele documentului în afara arborelui documentului fie cu DocumentFragments, fie pur și simplu prin detașarea/reatașarea elementelor curente direct. Astfel de tehnici pot fi utilizate ca parte a șabloanelor JavaScript sau pentru a evita operațiuni excesive de repictare/reflux în timp ce elementele în cauză sunt modificate intens.

În mod similar, noua funcționalitate „Shadow DOM” care este implementată în browserele moderne permite ca elementele să facă parte din document, dar să nu poată fi interogate prin document.getElementById și toate metodele sale înrudite (querySelector, etc.). Acest lucru se face pentru a încapsula funcționalitatea și a o ascunde în mod specific.

Totuși, este foarte probabil ca elementul pe care îl cauți pur și simplu să nu fie (încă) în document, și ar trebui să faci așa cum sugerează Felix. Totuși, ar trebui să fii conștient și că aceasta nu este tot mai mult singura cauză pentru care un element ar putea fi de negăsit (fie temporar, fie permanent).

Răspunsul #6

Dacă ordinea de execuție a scriptului nu este problema, o altă cauză posibilă a problemei este că elementul nu este selectat corect:

  • getElementById necesită ca șirul transmis să fie ID-ul literal, și nimic altceva. Dacă prefixezi șirul transmis cu un #, iar ID-ul nu începe cu un #, nimic nu va fi selectat:

      <div id="foo"></div>
    
      // Error, selected element will be null:
      document.getElementById('#foo')
      // Fix:
      document.getElementById('foo')
    
  • În mod similar, pentru getElementsByClassName, nu prefixa șirul transmis cu un .:

      <div class="bar"></div>
    
      // Error, selected element will be undefined:
      document.getElementsByClassName('.bar')[0]
      // Fix:
      document.getElementsByClassName('bar')[0]
    
  • Cu querySelector, querySelectorAll și jQuery, pentru a potrivi un element cu un anumit nume de clasă, pune un . direct înainte de clasă. În mod similar, pentru a potrivi un element cu un anumit ID, pune un # direct înainte de ID:

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

    Regulile aici sunt, în majoritatea cazurilor, identice cu cele pentru selectorii CSS și pot fi văzute în detaliu aici.

  • Pentru a potrivi un element care are două sau mai multe atribute (cum ar fi două nume de clasă, sau un nume de clasă și un atribut data-), pune selecțiile pentru fiecare atribut unul lângă altul în șirul selectorului, fără un spațiu care să le despartă (deoarece un spațiu indică selectorul descendent). De exemplu, pentru a selecta:

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

    folosește șirul de interogare .foo.bar. Pentru a selecta

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

    folosește șirul de interogare .foo[data-bar="someData"]. Pentru a selecta <span> de mai jos:

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

    folosește div.parent > span[data-username="bob"].

  • Majusculele și ortografia contează pentru toate cele de mai sus. Dacă majusculele sunt diferite sau ortografia este diferită, elementul nu va fi selectat:

      <div class="result"></div>
    
      // Error, selected element will be null:
      document.querySelector('.results')
      $('.Result')
      // Fix:
      document.querySelector('.result')
      $('.result')
    
  • De asemenea, trebuie să te asiguri că metodele au capitalizarea și ortografia corespunzătoare. Folosește unul dintre:

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

    Orice altă scriere sau utilizare a majusculelor nu va funcționa. De exemplu, document.getElementByClassName va genera o eroare.

  • Asigură-te că transmiți un șir către aceste metode de selectare. Dacă transmiți ceva care nu este un șir către querySelector, getElementById etc., aproape sigur nu va funcționa.

  • Dacă atributele HTML de pe elementele pe care doriți să le selectați sunt înconjurate de ghilimele, acestea trebuie să fie ghilimele drepte simple (fie simple, fie duble); ghilimelele curbate precum ‘ sau ” nu vor funcționa dacă încercați să selectați după ID, clasă sau atribut.