Dlaczego jQuery lub metoda DOM, taka jak getElementById, nie znajduje elementu?

483

Jakie są możliwe przyczyny document.getElementById, $("#id")lub jakakolwiek inna metoda DOM / selektor jQuery nie znalezienie elementów?

Przykładowe problemy obejmują:

  • jQuery po cichu nie powiąże procedury obsługi zdarzeń
  • jQuery metody "getter" ( .val(), .html(), .text()) powrocieundefined
  • Zwraca standardową metodę DOM, nullpowodując jeden z kilku błędów:

Uncaught TypeError: Nie można ustawić właściwości „...” o wartości null Uncaught TypeError: Nie można odczytać właściwości „...” o wartości null

Najczęstsze formy to:

Uncaught TypeError: Nie można ustawić właściwości „onclick” wartości null

Uncaught TypeError: Nie można odczytać właściwości „addEventListener” o wartości null

Uncaught TypeError: Nie można odczytać właściwości „style” wartości null

Felix Kling
źródło
32
Wiele pytań dotyczy tego, dlaczego nie znaleziono określonego elementu DOM, a przyczyną jest często to, że kod JavaScript jest umieszczony przed elementem DOM. To ma być kanoniczną odpowiedzią na tego typu pytania. To wiki społeczności, więc możesz je ulepszyć .
Felix Kling,

Odpowiedzi:

481

Element, który próbujesz znaleźć, nie był w DOM podczas uruchamiania skryptu.

Pozycja skryptu zależnego od DOM może mieć głęboki wpływ na jego zachowanie. Przeglądarki analizują dokumenty HTML od góry do dołu. Elementy są dodawane do DOM, a skrypty są (generalnie) uruchamiane w miarę ich napotykania. Oznacza to, że kolejność ma znaczenie. Zazwyczaj skrypty nie mogą znaleźć elementów, które pojawią się później w znacznikach, ponieważ elementy te nie zostały jeszcze dodane do DOM.

Rozważ następujące znaczniki; skrypt nr 1 nie może znaleźć, <div>dopóki skrypt nr 2 się powiedzie:

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

Co powinieneś zrobić? Masz kilka opcji:


Opcja 1: przenieś skrypt

Przenieś skrypt dalej w dół strony, tuż przed zamykającym tagiem treści. Zorganizowana w ten sposób reszta dokumentu jest analizowana przed wykonaniem skryptu:

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

Uwaga: Umieszczanie skryptów na dole jest ogólnie uważane za najlepszą praktykę .


Opcja 2: jQuery's ready()

Odłóż skrypt, aż DOM zostanie całkowicie przeanalizowany, używając :$(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>

Uwaga: Można po prostu wiążą się DOMContentLoadedalbo ale każdy ma swoje zastrzeżenia. jQuery's dostarcza rozwiązanie hybrydowe.window.onloadready()


Opcja 3: Delegacja wydarzenia

Zdarzenia delegowane mają tę zaletę, że mogą przetwarzać zdarzenia z elementów potomnych, które są dodawane do dokumentu w późniejszym czasie.

Gdy element wywołuje zdarzenie (pod warunkiem, że jest to wydarzenie bąbelkowe i nic nie powstrzymuje jego propagacji), każdy rodzic w przodku tego elementu również odbiera zdarzenie. To pozwala nam dołączyć moduł obsługi do istniejącego elementu i przykładowe zdarzenia, gdy pojawiają się one z jego potomków ... nawet tych dodanych po podłączeniu modułu obsługi. Wszystko, co musimy zrobić, to sprawdzić zdarzenie, aby zobaczyć, czy zostało wywołane przez żądany element, a jeśli tak, uruchom nasz kod.

jQuery on()wykonuje dla nas tę logikę. Podajemy po prostu nazwę zdarzenia, selektor żądanego potomka i moduł obsługi zdarzenia:

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

Uwaga: Zazwyczaj ten wzór jest zarezerwowany dla elementów, które nie istniały w czasie ładowania lub aby uniknąć dołączania dużej liczby procedur obsługi. Warto również zauważyć, że chociaż dołączyłem przewodnik document(w celach demonstracyjnych), należy wybrać najbliższego wiarygodnego przodka.


Opcja 4: deferAtrybut

Użyj deferatrybutu <script>.

[ defer, atrybut boolowski,] jest ustawiony tak, aby wskazywał przeglądarce, że skrypt ma zostać wykonany po przeanalizowaniu dokumentu, ale przed uruchomieniem DOMContentLoaded.

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

Dla odniesienia, oto kod z tego zewnętrznego skryptu :

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

Uwaga: deferAtrybut z pewnością wygląda jak magiczna kula, ale należy pamiętać o zastrzeżeniach ...
1. deferMożna go używać tylko w przypadku zewnętrznych skryptów, tj. Tych, które mają srcatrybut.
2. być świadomym obsługi przeglądarki , tzn .: implementacji błędów w IE <10

kanon
źródło
Jest teraz 2020 - czy „Umieszczanie skryptów na dole” jest nadal „uważane za najlepszą praktykę”? Ja (teraz) wkładam wszystkie swoje zasoby <head>i używam deferskryptów (nie muszę obsługiwać starych, niezgodnych przeglądarek)
Stephen P
Jestem wielkim zwolennikiem przejścia na nowoczesne przeglądarki. To powiedziawszy, ta praktyka tak naprawdę nic mnie nie kosztuje i po prostu działa wszędzie. Z drugiej strony, zarówno deferi asyncmają dość szerokie wsparcie -Nawet sięgającą niektórych znacznie starszych wersji przeglądarek. Mają dodatkową zaletę polegającą na wyraźnym, wyraźnym sygnalizowaniu zamierzonego zachowania. Pamiętaj tylko, że te atrybuty działają tylko w przypadku skryptów określających srcatrybut, tj. Skryptów zewnętrznych. Zważyć korzyści. Może użyć obu? Twoja decyzja.
kanon
146

Krótko i prosto: ponieważ elementy, których szukasz, nie istnieją w dokumencie (jeszcze).


Dla pozostałej części tej odpowiedzi użyję getElementByIdjako przykładu, ale to samo dotyczy getElementsByTagName, querySelectori każda inna metoda elementów DOM że wybiera.

Możliwe przyczyny

Istnieją dwa powody, dla których element może nie istnieć:

  1. Element z przekazanym identyfikatorem naprawdę nie istnieje w dokumencie. Powinieneś dokładnie sprawdzić, czy identyfikator, który przekazujesz, getElementByIdnaprawdę pasuje do identyfikatora istniejącego elementu w (wygenerowanym) kodzie HTML i czy nie wpisałeś błędnej nazwy (w identyfikatorach rozróżniana jest wielkość liter !).

    Nawiasem mówiąc, w większości współczesnych przeglądarek , które implementują querySelector()i querySelectorAll()metody, notacja w stylu CSS jest używana do pobierania elementu przez jego id, na przykład: document.querySelector('#elementID')w przeciwieństwie do metody, za pomocą której element jest pobierany przez jego idpod document.getElementById('elementID'); w pierwszej #postać jest niezbędna, w drugiej prowadziłaby do nieodzyskiwania elementu.

  2. Element nie istnieje w momencie, w którym dzwonisz getElementById.

Ten drugi przypadek jest dość powszechny. Przeglądarki parsują i przetwarzają HTML od góry do dołu. Oznacza to, że każde wywołanie elementu DOM, które nastąpi przed pojawieniem się tego elementu DOM w kodzie HTML, zakończy się niepowodzeniem.

Rozważ następujący przykład:

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

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

divPojawia się poscript . W momencie wykonania skryptu element jeszcze nie istnieje i getElementByIdpowróci null.

jQuery

To samo dotyczy wszystkich selektorów z jQuery. jQuery nie znajdzie elementów, jeśli źle wpisałeś selektor lub próbujesz je wybrać, zanim one faktycznie istnieją .

Dodatkową niespodzianką jest to, że jQuery nie został znaleziony, ponieważ skrypt został załadowany bez protokołu i działa z systemu plików:

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

ta składnia służy do umożliwienia załadowania skryptu przez HTTPS na stronie z protokołem https: // oraz do załadowania wersji HTTP na stronie z protokołem http: //

Ma niefortunny efekt uboczny próby załadowania i niepowodzenia file://somecdn.somewhere.com...


Rozwiązania

Przed wykonaniem wywołania getElementById(lub jakiejkolwiek innej metody DOM w tym zakresie) upewnij się, że istnieją elementy, do których chcesz uzyskać dostęp, tzn. Że DOM jest załadowany.

Można to zapewnić, umieszczając JavaScript za odpowiednim elementem DOM

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

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

w takim przypadku możesz również wstawić kod tuż przed zamykającym tagiem body ( </body>) (wszystkie elementy DOM będą dostępne w momencie wykonywania skryptu).

Inne rozwiązania obejmują nasłuchiwanie zdarzeń load [MDN] lub DOMContentLoaded [MDN] . W takich przypadkach nie ma znaczenia, gdzie w dokumencie umieszczasz kod JavaScript, musisz tylko pamiętać o umieszczeniu całego kodu przetwarzania DOM w procedurach obsługi zdarzeń.

Przykład:

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

// or

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

Więcej informacji na temat obsługi zdarzeń i różnic w przeglądarkach można znaleźć w artykułach na quirksmode.org .

jQuery

Najpierw upewnij się, że jQuery jest poprawnie załadowany. Skorzystaj z narzędzi programistycznych przeglądarki, aby dowiedzieć się, czy plik jQuery został znaleziony, a jeśli nie, popraw adres URL (np. Dodaj schemat http:lub https:na początku, dostosuj ścieżkę itp.)

Słuchanie load/ DOMContentLoaded events jest dokładnie tym, co robi jQuery z .ready() [docs] . Cały kod jQuery, który wpływa na element DOM, powinien znajdować się w module obsługi zdarzeń.

W rzeczywistości samouczek jQuery wyraźnie stwierdza:

Ponieważ prawie wszystko, co robimy podczas korzystania z jQuery, czyta lub manipuluje modelem obiektowym dokumentu (DOM), musimy upewnić się, że zaczniemy dodawać zdarzenia itp., Gdy tylko DOM będzie gotowy.

Aby to zrobić, rejestrujemy gotowe wydarzenie dla dokumentu.

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

Alternatywnie możesz również użyć składni skróconej:

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

Oba są równoważne.

Felix Kling
źródło
1
Czy warto zmienić ostatni fragment readywydarzenia, aby odzwierciedlić preferowaną składnię „nowszą”, czy też lepiej pozostawić ją taką, jaka jest, aby działała w starszych wersjach?
Tieson T.,
1
Dla jQuery, warto też dodać ewentualnie $(window).load()(i ewentualnie składniowe alternatywy dla $(document).ready(), $(function(){})Wydaje powiązane, ale to? Czuje lekko styczna do punktu robisz.
mówi David dozbrojenie Monica
@David: Dobra uwaga z .load. Jeśli chodzi o alternatywy składniowe, można je przejrzeć w dokumentacji, ale ponieważ odpowiedzi powinny być samodzielne, warto je dodać. Z drugiej strony jestem całkiem pewien, że ten konkretny fragment dotyczący jQuery został już odebrany i prawdopodobnie jest ujęty w innych odpowiedziach i połączenie z nim może wystarczyć.
Felix Kling,
1
@David: Muszę poprawić: Najwyraźniej .loadjest przestarzały: api.jquery.com/load-event . Nie wiem, czy dotyczy to tylko zdjęć, czy windowteż.
Felix Kling,
1
@David: Bez obaw :) Nawet jeśli nie jesteś pewien, dobrze, że o tym wspomniałeś. Prawdopodobnie dodam tylko kilka prostych fragmentów kodu, aby pokazać użycie. Dzięki za wkład!
Felix Kling,
15

Powody, dla których selektory oparte na identyfikatorze nie działają

  1. Element / DOM o podanym identyfikatorze jeszcze nie istnieje.
  2. Element istnieje, ale nie jest zarejestrowany w DOM [w przypadku węzłów HTML dołączanych dynamicznie z odpowiedzi Ajax].
  3. Obecny jest więcej niż jeden element o tym samym identyfikatorze, który powoduje konflikt.

Rozwiązania

  1. Spróbuj uzyskać dostęp do elementu po jego deklaracji lub alternatywnie użyj rzeczy takich jak $(document).ready();

  2. W przypadku elementów pochodzących z odpowiedzi Ajax, użyj .bind()metody jQuery. Starsze wersje jQuery miały .live()to samo.

  3. Użyj narzędzi [na przykład wtyczki dla programistów do przeglądarek], aby znaleźć zduplikowane identyfikatory i je usunąć.

sumit
źródło
13

Jak wskazał @FelixKling, najbardziej prawdopodobnym scenariuszem jest to, że węzły, których szukasz, nie istnieją (jeszcze).

Jednak współczesne praktyki programistyczne często mogą manipulować elementami dokumentu poza drzewem dokumentu albo za pomocą DocumentFragments, albo po prostu bezpośrednio odłączając / ponownie podłączając bieżące elementy. Takie techniki mogą być stosowane jako część szablonów JavaScript lub w celu uniknięcia nadmiernych operacji odświeżania / ponownego wlewania, podczas gdy elementy są mocno zmieniane.

Podobnie, nowa funkcja „Shadow DOM” wdrażana w nowoczesnych przeglądarkach pozwala elementom wchodzić w skład dokumentu, ale nie można go wyszukiwać w document.getElementById i wszystkich jego metodach rodzeństwa (querySelector itp.). Odbywa się to w celu enkapsulacji funkcjonalności i jej ukrywania.

Ponownie jednak najprawdopodobniej element, którego szukasz, po prostu nie ma (jeszcze) w dokumencie i powinieneś zrobić, jak sugeruje Felix. Należy jednak pamiętać, że coraz częściej nie jest to jedyny powód, dla którego element może być nieodnawialny (tymczasowo lub na stałe).

Nathan Bubna
źródło
13

Jeśli element, do którego próbujesz uzyskać dostęp, znajduje się w środku iframei spróbujesz uzyskać do niego dostęp poza kontekstem, iframespowoduje to również awarię.

Jeśli chcesz uzyskać element w ramce iframe, możesz dowiedzieć się, jak to zrobić tutaj .

George Mulligan
źródło
7

Jeśli kolejność wykonywania skryptu nie jest problemem, inną możliwą przyczyną problemu jest niewłaściwy wybór elementu:

  • getElementByIdwymaga, aby przekazany ciąg znaków był dosłownie identyfikatorem i nic więcej. Jeśli poprzedzony ciąg zostanie poprzedzony a #, a identyfikator nie zaczyna się od a #, nic nie zostanie wybrane:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
  • Podobnie, dla getElementsByClassName, nie poprzedzaj przekazanego ciągu ciągiem .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • W przypadku querySelector, querySelectorAll i jQuery, aby dopasować element o określonej nazwie klasy, wstaw .bezpośrednio przed klasą. Podobnie, aby dopasować element o określonym identyfikatorze, umieść #bezpośrednio przed identyfikatorem:

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

    Reguły tutaj są w większości przypadków identyczne z tymi dla selektorów CSS i można je szczegółowo zobaczyć tutaj .

  • Aby dopasować element, który ma dwa lub więcej atrybutów (np. Dwie nazwy klas lub nazwę klasy i data-atrybut), umieść selektory dla każdego atrybutu obok siebie w ciągu selektora, bez spacji oddzielającej je (ponieważ spacja wskazuje selektor potomka ). Na przykład, aby wybrać:

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

    użyj ciągu zapytania .foo.bar. Wybrać

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

    użyj ciągu zapytania .foo[data-bar="someData"]. Aby wybrać <span>poniżej:

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

    użyć div.parent > span[data-username="bob"].

  • Wielkie litery i pisownia mają znaczenie dla wszystkich powyższych. Jeśli wielkie litery lub inna pisownia są różne, element nie zostanie wybrany:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • Musisz także upewnić się, że metody mają odpowiednią wielkość liter i pisownię. Użyj jednego z:

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

    Żadna inna pisownia lub wielkie litery nie będą działać. Na przykład document.getElementByClassNamewyrzuci błąd.

  • Upewnij się, że przekazujesz ciąg do tych metod wyboru. Jeśli coś przekazać, że nie jest ciągiem do querySelector, getElementByIditp, to prawie na pewno nie będzie działać.

CertainPerformance
źródło
0

Kiedy wypróbowałem twój kod, zadziałało.

Jedynym powodem, dla którego Twoje wydarzenie nie działa, może być fakt, że Twój DOM nie był gotowy, a przycisk o identyfikatorze „event-btn” nie był jeszcze gotowy. Twój skrypt javascript został wykonany i próbował powiązać zdarzenie z tym elementem.

Przed użyciem elementu DOM do wiązania element ten powinien być gotowy. Można to zrobić na wiele sposobów.

Opcja 1: Możesz przenieść kod powiązania zdarzenia w ramach zdarzenia gotowości do dokumentu. Lubić:

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

Opcja 2: Możesz użyć zdarzenia przekroczenia limitu czasu, aby wiązanie zostało opóźnione o kilka sekund. lubić:

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

Opcja 3: przenieś swój skrypt javascript na dół strony.

Mam nadzieję, że to Ci pomoże.

Jatinder Kumar
źródło
@ Willdomybest18, sprawdź tę odpowiedź
Jatinder Kumar
-1

problem polega na tym, że element „speclist” nie jest tworzony w czasie wykonywania kodu javascript. Więc wstawiłem kod javascript do funkcji i wywołałem tę funkcję w przypadku zdarzenia obciążenia ciała.

function do_this_first(){
   //appending code
}

<body onload="do_this_first()">
</body>
Mia Davis
źródło
-1

Moje rozwiązanie było nie używać id elementu zakotwiczenia: <a id='star_wars'>Place to jump to</a>. Najwyraźniej blazor i inne struktury spa mają problemy z przeskakiwaniem do kotwic na tej samej stronie. Aby się obejść, musiałem użyć document.getElementById('star_wars'). Jednak to nie działa aż położę id w elemencie ust zamiast: <p id='star_wars'>Some paragraph<p>.

Przykład użycia 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
źródło