Mengapa jQuery atau metode DOM seperti getElementById tidak menemukan elemen?

483

Apa alasan yang memungkinkan document.getElementById, $("#id")atau metode DOM / pemilih jQuery lainnya yang tidak menemukan elemen?

Contoh masalah termasuk:

  • jQuery diam-diam gagal mengikat event handler
  • jQuery "pengambil" metode ( .val(), .html(), .text()) kembaliundefined
  • Metode DOM standar yang nullmenghasilkan beberapa kesalahan:

Uncaught TypeError: Tidak dapat mengatur properti '...' dari null Uncaught TypeError: Tidak dapat membaca properti '...' dari nol

Bentuk yang paling umum adalah:

UnEught TypeError: Tidak dapat mengatur properti 'onclick' dari nol

UnEught TypeError: Tidak dapat membaca properti 'addEventListener' dari nol

UnEught TypeError: Tidak dapat membaca properti 'style' dari nol

Felix Kling
sumber
32
Banyak pertanyaan yang diajukan tentang mengapa elemen DOM tertentu tidak ditemukan dan alasannya sering karena kode JavaScript ditempatkan sebelum elemen DOM. Ini dimaksudkan sebagai jawaban kanonik untuk jenis pertanyaan ini. Ini wiki komunitas, jadi silakan memperbaikinya .
Felix Kling

Jawaban:

481

Elemen yang Anda coba temukan tidak ada di DOM saat skrip Anda berjalan.

Posisi skrip yang bergantung pada DOM Anda dapat memiliki efek mendalam pada perilakunya. Browser mem-parsing dokumen HTML dari atas ke bawah. Elemen ditambahkan ke DOM dan skrip (umumnya) dieksekusi saat ditemui. Ini berarti ketertiban penting. Biasanya, skrip tidak dapat menemukan elemen yang muncul kemudian di markup karena elemen-elemen tersebut belum ditambahkan ke DOM.

Pertimbangkan markup berikut; skrip # 1 gagal menemukan <div>sedangkan skrip # 2 berhasil:

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

Jadi, apa yang harus kamu lakukan? Anda punya beberapa opsi:


Opsi 1: Pindahkan skrip Anda

Pindahkan skrip Anda lebih jauh ke bawah halaman, tepat sebelum tag penutup tubuh. Diatur dengan cara ini, sisa dokumen diuraikan sebelum skrip Anda dijalankan:

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

Catatan: Menempatkan skrip di bagian bawah umumnya dianggap praktik terbaik .


Opsi 2: jQuery's ready()

Tunda skrip Anda hingga DOM selesai diuraikan, menggunakan :$(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>

Catatan: Anda bisa saja mengikat DOMContentLoadedatau tetapi masing-masing memiliki peringatan. jQuery's memberikan solusi hybrid.window.onloadready()


Opsi 3: Delegasi Acara

Peristiwa yang didelegasikan memiliki keuntungan bahwa mereka dapat memproses peristiwa dari elemen turunan yang ditambahkan ke dokumen di lain waktu.

Ketika sebuah elemen memunculkan sebuah event (asalkan itu adalah event yang menggelegak dan tidak ada yang menghentikan penyebarannya), masing-masing orangtua dalam leluhur elemen tersebut menerima event tersebut juga. Itu memungkinkan kita untuk melampirkan pawang ke elemen yang ada dan contoh peristiwa saat mereka muncul dari keturunannya ... bahkan yang ditambahkan setelah pawang dilampirkan. Yang harus kita lakukan adalah memeriksa acara untuk melihat apakah itu dibangkitkan oleh elemen yang diinginkan dan, jika demikian, jalankan kode kita.

jQuery on()melakukan logika itu untuk kita. Kami hanya menyediakan nama acara, pemilih untuk keturunan yang diinginkan, dan pengendali acara:

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

Catatan: Biasanya, pola ini dicadangkan untuk elemen yang tidak ada pada waktu buka atau untuk menghindari melampirkan sejumlah besar penangan. Penting juga menunjukkan bahwa sementara saya telah melampirkan pawang ke document(untuk tujuan demonstratif), Anda harus memilih leluhur tepercaya terdekat.


Opsi 4: deferAtribut

Gunakan deferatribut <script>.

[ defer, atribut Boolean,] diatur untuk menunjukkan ke browser bahwa skrip dimaksudkan untuk dieksekusi setelah dokumen diuraikan, tetapi sebelum diaktifkan DOMContentLoaded.

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

Untuk referensi, inilah kode dari skrip eksternal itu :

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

Catatan: deferAtribut tentu saja tampak seperti peluru ajaib tetapi penting untuk menyadari peringatan ...
1. deferhanya dapat digunakan untuk skrip eksternal, yaitu: mereka yang memiliki srcatribut.
2. Waspadai dukungan browser , yaitu: implementasi buggy di IE <10

kanon
sumber
Sekarang tahun 2020 - apakah "Menempatkan skrip di bagian bawah" masih "dianggap sebagai praktik terbaik"? Saya (saat ini) memasukkan semua sumber daya saya <head>dan menggunakan deferskrip (saya tidak harus mendukung browser yang tidak kompatibel lama-buruk)
Stephen P
Saya seorang pendukung besar untuk pindah ke browser modern. Yang mengatakan, latihan ini tidak benar-benar merugikan saya dan hanya bekerja di mana-mana. Di sisi lain, keduanya deferdan asyncmemiliki dukungan yang cukup luas - bahkan mencapai beberapa versi peramban yang jauh lebih tua. Mereka memiliki keuntungan tambahan dengan jelas, secara eksplisit menandakan perilaku yang dimaksud. Ingat saja bahwa atribut ini hanya berfungsi untuk skrip yang menetapkan srcatribut, yaitu: skrip eksternal. Timbang manfaatnya. Mungkin menggunakan keduanya? Panggilanmu.
kanon
146

Singkat dan sederhana: Karena elemen yang Anda cari belum ada dalam dokumen (belum).


Untuk sisa jawaban ini saya akan menggunakan getElementByIdsebagai contoh, tetapi hal yang sama berlaku untuk getElementsByTagName, querySelectordan metode DOM lainnya yang memilih elemen.

Kemungkinan Alasan

Ada dua alasan mengapa suatu elemen mungkin tidak ada:

  1. Elemen dengan ID yang diteruskan benar-benar tidak ada dalam dokumen. Anda harus mengecek apakah ID yang Anda berikan getElementByIdbenar - benar cocok dengan ID dari elemen yang ada di HTML (yang dihasilkan) dan Anda belum salah mengeja ID (ID peka huruf besar kecil !).

    Secara kebetulan, di sebagian besar browser kontemporer , yang menerapkan querySelector()dan querySelectorAll()metode, notasi gaya-CSS digunakan untuk mengambil elemen dengan id, misalnya document.querySelector('#elementID'):, sebagai lawan dari metode dimana elemen diambil oleh di idbawahnya document.getElementById('elementID'); pada karakter pertama #sangat penting, pada karakter kedua akan menyebabkan elemen tidak diambil.

  2. Elemen tidak ada pada saat Anda menelepon getElementById.

Kasus terakhir cukup umum. Browser mem-parsing dan memproses HTML dari atas ke bawah. Itu berarti bahwa setiap panggilan ke elemen DOM yang terjadi sebelum elemen DOM muncul di HTML, akan gagal.

Perhatikan contoh berikut:

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

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

The divmuncul setelah para script. Pada saat script dijalankan, elemen tidak ada belum dan getElementByIdakan kembali null.

jQuery

Hal yang sama berlaku untuk semua penyeleksi dengan jQuery. jQuery tidak akan menemukan elemen jika Anda salah mengeja pemilih atau Anda mencoba memilihnya sebelum benar-benar ada .

Sentuhan tambahan adalah ketika jQuery tidak ditemukan karena Anda telah memuat skrip tanpa protokol dan berjalan dari sistem file:

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

sintaks ini digunakan untuk memungkinkan skrip memuat melalui HTTPS pada halaman dengan protokol https: // dan untuk memuat versi HTTP pada halaman dengan protokol http: //

Ini memiliki efek samping yang disayangkan ketika mencoba dan gagal memuat file://somecdn.somewhere.com...


Solusi

Sebelum Anda melakukan panggilan ke getElementById(atau metode DOM apa pun), pastikan elemen yang ingin Anda akses ada, yaitu DOM dimuat.

Ini dapat dipastikan dengan hanya menempatkan JavaScript Anda setelah elemen DOM yang sesuai

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

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

dalam hal ini Anda juga dapat meletakkan kode tepat sebelum tag penutup tubuh ( </body>) (semua elemen DOM akan tersedia pada saat skrip dijalankan).

Solusi lain termasuk mendengarkan acara load [MDN] atau DOMContentLoaded [MDN] . Dalam kasus ini, tidak masalah di mana dalam dokumen Anda menempatkan kode JavaScript, Anda hanya harus ingat untuk meletakkan semua kode pemrosesan DOM dalam event handler.

Contoh:

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

// or

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

Silakan lihat artikel di quirksmode.org untuk informasi lebih lanjut mengenai penanganan acara dan perbedaan browser.

jQuery

Pertama, pastikan jQuery dimuat dengan benar. Gunakan alat pengembang browser untuk mencari tahu apakah file jQuery ditemukan dan perbaiki URL jika tidak (mis. Tambahkan skema http:atau https:di awal, sesuaikan jalur, dll.)

Mendengarkan load/ DOMContentLoaded events adalah persis apa yang dilakukan jQuery dengan .ready() [docs] . Semua kode jQuery Anda yang memengaruhi elemen DOM harus ada di dalam pengendali event tersebut.

Bahkan, tutorial jQuery secara eksplisit menyatakan:

Karena hampir semua yang kita lakukan saat menggunakan jQuery membaca atau memanipulasi model objek dokumen (DOM), kita perlu memastikan bahwa kita mulai menambahkan peristiwa dll segera setelah DOM siap.

Untuk melakukan ini, kami mendaftarkan acara yang siap untuk dokumen.

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

Atau Anda juga dapat menggunakan sintaks steno:

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

Keduanya setara.

Felix Kling
sumber
1
Apakah layak mengubah sedikit terakhir tentang readyacara untuk mencerminkan sintaks yang disukai "baru", atau lebih baik membiarkannya apa adanya sehingga berfungsi di versi yang lebih lama?
Tieson T.
1
Untuk jQuery, apakah itu layak juga menambahkan dalam alternatif $(window).load()(dan alternatif mungkin sintaksis dengan $(document).ready(), $(function(){})itu tampaknya terkait, tapi? Terasa sedikit tangensial ke titik Anda membuat Anda.
David mengatakan mengembalikan Monica
@ David: Poin bagus dengan .load. Mengenai alternatif sintaks, mereka dapat dicari dalam dokumentasi tetapi karena jawaban harus lengkap, mungkin sebenarnya layak untuk ditambahkan. Kemudian lagi, saya cukup yakin ini sedikit spesifik tentang jQuery sudah dijawab dan mungkin tercakup dalam jawaban lain dan menghubungkannya mungkin sudah cukup.
Felix Kling
1
@ David: Saya harus merevisi: Rupanya .loadsudah usang: api.jquery.com/load-event . Saya tidak tahu apakah ini untuk gambar saja atau windowjuga.
Felix Kling
1
@ David: Jangan khawatir :) Meskipun Anda tidak yakin, ada baiknya Anda menyebutkannya. Saya mungkin akan menambahkan hanya beberapa cuplikan kode sederhana untuk menunjukkan penggunaannya. Terima kasih atas masukan Anda!
Felix Kling
15

Alasan mengapa pemilih berbasis id tidak berfungsi

  1. Elemen / DOM dengan id yang ditentukan belum ada.
  2. Elemen itu ada, tetapi tidak terdaftar di DOM [dalam hal node HTML ditambahkan secara dinamis dari respons Ajax].
  3. Ada lebih dari satu elemen dengan id yang sama yang menyebabkan konflik.

Solusi

  1. Coba akses elemen setelah deklarasi atau gunakan hal-hal seperti $(document).ready();

  2. Untuk elemen yang berasal dari respons Ajax, gunakan .bind()metode jQuery. Versi jQuery yang lebih lama memiliki .live()hal yang sama.

  3. Gunakan alat [misalnya, plugin pengembang web untuk browser] untuk menemukan id duplikat dan menghapusnya.

sumit
sumber
13

Seperti yang ditunjukkan oleh @FelixKling, skenario yang paling mungkin adalah bahwa node yang Anda cari belum ada (belum).

Namun, praktik pembangunan modern sering dapat memanipulasi elemen dokumen di luar pohon dokumen baik dengan DocumentFragments atau hanya melepaskan / memasang kembali elemen saat ini secara langsung. Teknik-teknik semacam itu dapat digunakan sebagai bagian dari templating JavaScript atau untuk menghindari operasi pengecatan ulang yang berlebihan sementara elemen-elemen yang dipertanyakan sedang banyak diubah.

Demikian pula, fungsionalitas "Shadow DOM" baru yang digulirkan di browser modern memungkinkan elemen menjadi bagian dari dokumen, tetapi tidak dapat ditanyakan oleh document.getElementById dan semua metode saudaranya (querySelector, dll.). Ini dilakukan untuk merangkum fungsionalitas dan secara khusus menyembunyikannya.

Namun, sekali lagi, kemungkinan besar unsur yang Anda cari sebenarnya belum ada di dokumen, dan Anda harus melakukan seperti yang disarankan Felix. Namun, Anda juga harus menyadari bahwa itu bukan satu-satunya alasan bahwa suatu elemen mungkin tidak dapat dihindarkan (baik sementara atau permanen).

Nathan Bubna
sumber
13

Jika elemen yang Anda coba akses berada di dalam iframedan Anda mencoba mengaksesnya di luar konteks iframeini juga akan menyebabkannya gagal.

Jika Anda ingin mendapatkan elemen dalam iframe, Anda bisa mengetahui caranya di sini .

George Mulligan
sumber
7

Jika urutan eksekusi skrip bukan masalah, kemungkinan penyebab lain masalahnya adalah bahwa elemen tidak dipilih dengan benar:

  • getElementByIdmembutuhkan string yang lewat menjadi ID kata demi kata , dan bukan yang lain. Jika Anda #awali string yang diteruskan dengan , dan ID tidak dimulai dengan #, tidak ada yang akan dipilih:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
    
  • Demikian pula, untuk getElementsByClassName, jangan awali string yang diteruskan dengan .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • Dengan kueriSelektor, kueriSelektorAll, dan jQuery, untuk mencocokkan elemen dengan nama kelas tertentu, letakkan .langsung di depan kelas. Demikian pula, untuk mencocokkan elemen dengan ID tertentu, letakkan #langsung di depan ID:

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

    Aturan di sini, dalam banyak kasus, identik dengan yang untuk penyeleksi CSS, dan dapat dilihat secara rinci di sini .

  • Untuk mencocokkan elemen yang memiliki dua atribut atau lebih (seperti dua nama kelas, atau nama kelas dan data-atribut), letakkan pemilih untuk setiap atribut di sebelah satu sama lain dalam string pemilih, tanpa spasi yang memisahkannya (karena spasi menunjukkan yang keturunan pemilih ). Misalnya, untuk memilih:

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

    gunakan string kueri .foo.bar. Memilih

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

    gunakan string kueri .foo[data-bar="someData"]. Untuk memilih di <span>bawah ini:

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

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

  • Kapitalisasi dan ejaan memang penting untuk semua hal di atas. Jika kapitalisasi berbeda, atau ejaannya berbeda, elemen tidak akan dipilih:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • Anda juga perlu memastikan metode memiliki huruf besar dan huruf yang benar. Gunakan salah satu dari:

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

    Ejaan atau huruf besar apa pun lainnya tidak akan berfungsi. Misalnya, document.getElementByClassNameakan melempar kesalahan.

  • Pastikan Anda meneruskan string ke metode pemilih ini. Jika Anda melewatkan sesuatu yang tidak string untuk querySelector, getElementById, dll, itu hampir pasti tidak akan bekerja.

Performa Tertentu
sumber
0

Ketika saya mencoba kode Anda, itu berhasil.

Satu-satunya alasan bahwa acara Anda tidak berfungsi, mungkin karena DOM Anda belum siap dan tombol Anda dengan id "event-btn" belum siap. Dan javascript Anda dieksekusi dan mencoba untuk mengikat acara dengan elemen itu.

Sebelum menggunakan elemen DOM untuk mengikat, elemen itu harus siap. Ada banyak opsi untuk melakukan itu.

Option1: Anda dapat memindahkan kode penjilidan acara Anda dalam acara siap dokumen. Suka:

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

Option2: Anda dapat menggunakan acara batas waktu, sehingga pengikatan tertunda selama beberapa detik. Suka:

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

Option3: pindahkan javascript Anda ke bagian bawah halaman Anda.

Saya harap ini membantu Anda.

Jatinder Kumar
sumber
@ Willdomybest18, silakan periksa jawaban ini
Jatinder Kumar
-1

masalahnya adalah elemen dom 'speclist' tidak dibuat pada saat kode javascript dieksekusi. Jadi saya memasukkan kode javascript ke dalam sebuah fungsi dan memanggil fungsi itu pada event onload tubuh.

function do_this_first(){
   //appending code
}

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

Solusi saya adalah untuk tidak menggunakan id dari elemen anchor: <a id='star_wars'>Place to jump to</a>. Rupanya pisau cukur dan kerangka spa lainnya memiliki masalah melompat ke jangkar di halaman yang sama. Untuk menyiasati itu saya harus menggunakan document.getElementById('star_wars'). Namun hal ini tidak bekerja sampai aku meletakkan id dalam elemen paragraf sebagai gantinya: <p id='star_wars'>Some paragraph<p>.

Contoh menggunakan 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
sumber