Tại sao jQuery hoặc một phương thức DOM như getEuityById không tìm thấy phần tử?


483

Những lý do có thể cho là gì document.getElementById, $("#id")hoặc bất kỳ / chọn phương pháp DOM khác jQuery không tìm thấy các yếu tố?

Các vấn đề ví dụ bao gồm:

  • jQuery âm thầm không liên kết một trình xử lý sự kiện
  • jQuery "getter" phương pháp ( .val(), .html(), .text()) trở vềundefined
  • Một phương thức DOM tiêu chuẩn trả về nulldẫn đến bất kỳ lỗi nào:

Uncaught TypeError: Không thể đặt thuộc tính '...' của null Uncaught TypeError: Không thể đọc thuộc tính '...' của null

Các hình thức phổ biến nhất là:

Uncaught TypeError: Không thể đặt thuộc tính 'onclick' của null

Uncaught TypeError: Không thể đọc thuộc tính 'addEventListener' của null

Uncaught TypeError: Không thể đọc thuộc tính 'style' của null


32
Nhiều câu hỏi được hỏi về lý do tại sao không tìm thấy một phần tử DOM nào đó và lý do thường là do mã JavaScript được đặt trước phần tử DOM. Đây được dự định là một câu trả lời chính tắc cho các loại câu hỏi. Đó là wiki cộng đồng, vì vậy xin vui lòng cải thiện nó .
Felix Kling

Câu trả lời:


481

Yếu tố bạn đang cố gắng tìm không có trong DOM khi tập lệnh của bạn chạy.

Vị trí của tập lệnh dựa trên DOM của bạn có thể có ảnh hưởng sâu sắc đến hành vi của nó. Trình duyệt phân tích các tài liệu HTML từ trên xuống dưới. Các phần tử được thêm vào DOM và các tập lệnh (thường) được thực thi khi chúng gặp phải. Điều này có nghĩa là vấn đề trật tự. Thông thường, các tập lệnh không thể tìm thấy các phần tử xuất hiện sau trong phần đánh dấu vì các phần tử đó vẫn chưa được thêm vào DOM.

Hãy xem xét các đánh dấu sau đây; tập lệnh số 1 không tìm thấy <div>tập lệnh trong khi tập lệnh số 2 thành công:

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

Vậy bạn nên làm gì? Bạn có một vài lựa chọn:


Tùy chọn 1: Di chuyển tập lệnh của bạn

Di chuyển tập lệnh của bạn xuống sâu hơn trang, ngay trước thẻ đóng. Được tổ chức theo kiểu này, phần còn lại của tài liệu được phân tích cú pháp trước khi tập lệnh của bạn được thực thi:

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

Lưu ý: Đặt các tập lệnh ở phía dưới thường được coi là một cách thực hành tốt nhất .


Tùy chọn 2: jQuery ready()

Trì hoãn tập lệnh của bạn cho đến khi DOM được phân tích cú pháp hoàn toàn, sử dụng :$(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>

Lưu ý: Bạn chỉ có thể liên kết với DOMContentLoadedhoặc nhưng mỗi người đều có báo trước của nó. jQuery cung cấp một giải pháp lai.window.onloadready()


Cách 3: Đoàn sự kiện

Các sự kiện được ủy quyền có lợi thế là chúng có thể xử lý các sự kiện từ các phần tử hậu duệ được thêm vào tài liệu sau đó.

Khi một phần tử làm phát sinh một sự kiện (với điều kiện đó là một sự kiện sủi bọt và không có gì ngăn cản sự lan truyền của nó), mỗi cha mẹ trong tổ tiên của phần tử đó cũng nhận được sự kiện đó. Điều đó cho phép chúng ta gắn một trình xử lý vào một phần tử hiện có và các sự kiện mẫu khi chúng nổi lên từ hậu duệ của nó ... ngay cả những phần được thêm vào sau khi trình xử lý được đính kèm. Tất cả những gì chúng ta phải làm là kiểm tra sự kiện để xem liệu nó có được tăng bởi phần tử mong muốn hay không và nếu có thì hãy chạy mã của chúng ta.

jQuery on()thực hiện logic đó cho chúng tôi. Chúng tôi chỉ cần cung cấp tên sự kiện, bộ chọn cho hậu duệ mong muốn và trình xử lý sự kiện:

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

Lưu ý: Thông thường, mẫu này được dành riêng cho các phần tử không tồn tại trong thời gian tải hoặc để tránh gắn một số lượng lớn trình xử lý. Cũng đáng để chỉ ra rằng trong khi tôi đã gắn một trình xử lý document(cho mục đích trình diễn), bạn nên chọn tổ tiên đáng tin cậy gần nhất.


Tùy chọn 4: deferThuộc tính

Sử dụng các deferthuộc tính của <script>.

[ defer, một thuộc tính Boolean,] được đặt để chỉ ra cho trình duyệt rằng tập lệnh có nghĩa là được thực thi sau khi tài liệu được phân tích cú pháp, nhưng trước khi bắn DOMContentLoaded.

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

Để tham khảo, đây là mã từ tập lệnh bên ngoài đó :

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

Lưu ý: Các deferthuộc tính chắc chắn dường như giống như một viên đạn ma thuật nhưng điều quan trọng là phải nhận thức được những hãy cẩn thận ...
1. deferchỉ có thể được sử dụng cho các kịch bản bên ngoài, ví dụ: những người có một srcthuộc tính.
2. lưu ý về hỗ trợ trình duyệt , nghĩa là: triển khai lỗi trong IE <10


Bây giờ là năm 2020 - "Đặt các tập lệnh ở phía dưới" vẫn "được coi là một cách thực hành tốt nhất"? I (nowdays) đặt tất cả nguồn lực của tôi trong <head>và sử dụng defertrên các kịch bản (Tôi không phải hỗ trợ các trình duyệt không tương thích xấu tuổi)
Stephen P

Tôi là một người ủng hộ lớn để chuyển sang các trình duyệt hiện đại. Điều đó nói rằng, thực tế này không thực sự chi phí cho tôi bất cứ điều gì và nó chỉ hoạt động ở mọi nơi. Mặt khác, cả hai deferasynccó khá rộng hỗ trợ -even đạt lại một số phiên bản trình duyệt cũ hơn nhiều. Họ có lợi thế bổ sung của rõ ràng, báo hiệu một cách rõ ràng các hành vi mong muốn. Chỉ cần nhớ rằng các thuộc tính này chỉ hoạt động đối với các tập lệnh chỉ định một srcthuộc tính, tức là: các tập lệnh bên ngoài. Cân nhắc lợi ích. Có thể sử dụng cả hai? Cuộc gọi của bạn.
canon

146

Ngắn gọn và đơn giản: Bởi vì các yếu tố bạn đang tìm kiếm không tồn tại trong tài liệu (chưa).


Đối với phần còn lại của câu trả lời này, tôi sẽ sử dụng getElementByIdlàm ví dụ, nhưng cũng áp dụng getElementsByTagName, querySelectorvà bất kỳ phương pháp DOM khác mà các yếu tố lựa chọn.

Lý do có thể

Có hai lý do tại sao một yếu tố có thể không tồn tại:

  1. Một yếu tố với ID thông qua thực sự không tồn tại trong tài liệu. Bạn nên kiểm tra kỹ xem ID bạn chuyển có getElementByIdthực sự khớp với ID của một yếu tố hiện có trong HTML (được tạo) không và bạn đã không viết sai ID (ID có phân biệt chữ hoa chữ thường !).

    Ngẫu nhiên, trong phần lớn các trình duyệt hiện đại , có triển khai querySelector()querySelectorAll()phương thức, ký hiệu kiểu CSS được sử dụng để truy xuất một phần tử id, ví dụ: document.querySelector('#elementID')trái ngược với phương thức mà phần tử được lấy bởi phần tử idbên dưới document.getElementById('elementID'); trong lần đầu tiên #nhân vật là điều cần thiết, trong lần thứ hai, nó sẽ dẫn đến yếu tố không được lấy ra.

  2. Yếu tố không tồn tại tại thời điểm bạn gọi getElementById.

Trường hợp sau là khá phổ biến. Trình duyệt phân tích và xử lý HTML từ trên xuống dưới. Điều đó có nghĩa là mọi cuộc gọi đến một phần tử DOM xảy ra trước khi phần tử DOM đó xuất hiện trong HTML, sẽ thất bại.

Hãy xem xét ví dụ sau:

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

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

Các divxuất hiện sau khi các script. Tại thời điểm này kịch bản được thực thi, nguyên tố này không tồn tại được nêu ragetElementByIdsẽ trở lại null.

jQuery

Điều tương tự cũng áp dụng cho tất cả các bộ chọn với jQuery. jQuery sẽ không tìm thấy các phần tử nếu bạn viết sai chính tả của bạn hoặc bạn đang cố chọn chúng trước khi chúng thực sự tồn tại .

Một twist được thêm vào là khi không tìm thấy jQuery vì bạn đã tải tập lệnh mà không có giao thức và đang chạy từ hệ thống tệp:

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

Cú pháp này được sử dụng để cho phép các kịch bản để tải thông qua HTTPS trên một trang với giao thức https: // và để tải phiên bản HTTP trên một trang với giao thức http: //

Nó có tác dụng phụ đáng tiếc là cố gắng và không tải file://somecdn.somewhere.com...


Các giải pháp

Trước khi bạn thực hiện một cuộc gọi đến getElementById(hoặc bất kỳ phương pháp DOM cho rằng vấn đề), đảm bảo các yếu tố mà bạn muốn truy cập tồn tại, tức là DOM được nạp.

Điều này có thể được đảm bảo bằng cách chỉ cần đặt JavaScript của bạn sau phần tử DOM tương ứng

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

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

trong trường hợp đó, bạn cũng có thể đặt mã ngay trước thẻ body đóng ( </body>) (tất cả các thành phần DOM sẽ có sẵn tại thời điểm tập lệnh được thực thi).

Các giải pháp khác bao gồm nghe các sự kiện load [MDN] hoặc DOMContentLoaded [MDN] . Trong những trường hợp này, việc bạn đặt mã JavaScript ở đâu không quan trọng, bạn chỉ cần nhớ đặt tất cả mã xử lý DOM trong trình xử lý sự kiện.

Thí dụ:

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

// or

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

Xin vui lòng xem các bài viết tại quirksmode.org để biết thêm thông tin về xử lý sự kiện và sự khác biệt trình duyệt.

jQuery

Trước tiên hãy chắc chắn rằng jQuery được tải đúng cách. Sử dụng công cụ phát triển của trình duyệt để tìm hiểu xem các tập tin jQuery đã được tìm thấy và sửa URL nếu nó không (ví dụ như thêm http:hoặc https:chương trình ngay từ đầu, điều chỉnh con đường, vv)

Nghe load/ DOMContentLoaded sự kiện chính xác là những gì jQuery đang làm với.ready() [docs] . Tất cả các mã jQuery của bạn có ảnh hưởng đến phần tử DOM nên bên trong đó xử lý sự kiện.

Trong thực tế, hướng dẫn jQuery nêu rõ:

Hầu như mọi thứ chúng ta làm khi sử dụng jQuery đọc hoặc thao tác mô hình đối tượng tài liệu (DOM), chúng ta cần đảm bảo rằng chúng ta bắt đầu thêm các sự kiện, vv ngay khi DOM sẵn sàng.

Để làm điều này, chúng tôi đăng ký một sự kiện sẵn sàng cho tài liệu.

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

Ngoài ra, bạn cũng có thể sử dụng cú pháp tốc ký:

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

Cả hai đều là tương đương.


1
Nó có đáng để sửa đổi chút cuối cùng về readysự kiện này để phản ánh cú pháp ưa thích "mới hơn" hay tốt hơn là để nó như vậy để nó hoạt động trên các phiên bản cũ hơn?
Tieson T.

1
Đối với jQuery, nó cũng đáng để thêm vào các lựa chọn thay thế $(window).load()(và có thể là cú pháp thay thế cho $(document).ready(), $(function(){})có vẻ như có liên quan, nhưng nó cảm thấy hơi tiếp tuyến với điểm bạn đang thực hiện.
David nói phục hồi lại

@ David: Tốt điểm với .load. Về các lựa chọn thay thế cú pháp, chúng có thể được tra cứu trong tài liệu nhưng vì các câu trả lời nên được khép kín, nên thực sự có thể đáng để thêm chúng. Sau đó, một lần nữa, tôi khá chắc chắn rằng bit cụ thể này về jQuery đã được trả lời và có lẽ được đề cập trong các câu trả lời khác và liên kết đến nó có thể đủ.
Felix Kling

1
@David: Tôi phải sửa lại: Rõ ràng .loadlà không dùng nữa: api.jquery.com/load-event . Tôi không biết nếu nó chỉ dành cho hình ảnh window.
Felix Kling

1
@David: Đừng lo lắng :) Ngay cả khi bạn không chắc chắn, thật tốt khi bạn đề cập đến nó. Tôi có thể sẽ chỉ thêm một số đoạn mã đơn giản để hiển thị việc sử dụng. Cảm ơn vì đầu vào của bạn!
Felix Kling

15

Lý do tại sao id selectors dựa không làm việc

  1. Phần tử / DOM với id được chỉ định chưa tồn tại.
  2. Phần tử tồn tại, nhưng nó không được đăng ký trong DOM [trong trường hợp các nút HTML được nối động từ các phản hồi Ajax].
  3. Hơn một yếu tố với cùng id là hiện tại mà đang gây ra một cuộc xung đột.

Các giải pháp

  1. Cố gắng truy cập phần tử sau khi khai báo hoặc sử dụng công cụ khác như $(document).ready();

  2. Đối với các phần tử đến từ các phản hồi Ajax, hãy sử dụng .bind()phương thức jQuery. Phiên bản cũ của jQuery có .live()cho cùng.

  3. Sử dụng công cụ [ví dụ, webdeveloper plugin cho trình duyệt] để tìm id trùng lặp và loại bỏ chúng.


13

Như @FelixKling chỉ ra, kịch bản nhiều khả năng nhất là các nút bạn đang tìm kiếm không tồn tại (chưa).

Tuy nhiên, thực tiễn phát triển hiện đại thường có thể thao tác các phần tử tài liệu bên ngoài cây tài liệu bằng DocumentFragments hoặc chỉ đơn giản là tách / gắn lại các phần tử hiện tại trực tiếp. Các kỹ thuật như vậy có thể được sử dụng như một phần của khuôn mẫu JavaScript hoặc để tránh các hoạt động sơn lại / chỉnh lại quá mức trong khi các yếu tố được đề cập đang bị thay đổi nhiều.

Tương tự, chức năng "Shadow DOM" mới được triển khai trên các trình duyệt hiện đại cho phép các thành phần là một phần của tài liệu, nhưng không thể truy vấn bằng document.getEuityById và tất cả các phương thức anh chị em của nó (querySelector, v.v.). Điều này được thực hiện để đóng gói chức năng và đặc biệt ẩn nó.

Tuy nhiên, một lần nữa, rất có thể là yếu tố bạn đang tìm kiếm đơn giản là không có (chưa) trong tài liệu, và bạn nên làm như lời gợi ý của Felix. Tuy nhiên, bạn cũng nên lưu ý rằng ngày càng không phải là lý do duy nhất khiến một yếu tố có thể không bị ràng buộc (tạm thời hoặc vĩnh viễn).


13

Nếu yếu tố bạn đang cố truy cập nằm trong một iframevà bạn cố gắng truy cập nó bên ngoài bối cảnh củaiframe điều này cũng sẽ khiến nó bị lỗi.

Nếu bạn muốn nhận được một phần tử trong một iframe bạn có thể tìm hiểu làm thế nào đây .


7

Nếu thứ tự thực thi tập lệnh không phải là vấn đề, một nguyên nhân có thể khác của vấn đề là phần tử không được chọn đúng:

  • getElementByIdyêu cầu chuỗi đã qua phải là nguyên văn ID và không có gì khác. Nếu bạn đặt tiền tố cho chuỗi đã truyền bằng a #và ID không bắt đầu bằng a #, sẽ không có gì được chọn:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
  • Tương tự, getElementsByClassNamekhông, tiền tố chuỗi đã truyền với .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • Với querySelector, querySelectorAll và jQuery, để khớp một phần tử với một tên lớp cụ thể, đặt .trực tiếp trước lớp. Tương tự, để khớp một phần tử với một ID cụ thể, hãy đặt #trực tiếp trước ID:

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

    Các quy tắc ở đây, trong hầu hết các trường hợp, giống hệt với các quy tắc cho bộ chọn CSS và có thể được xem chi tiết tại đây .

  • Để khớp với một phần tử có hai hoặc nhiều thuộc tính (như hai tên lớp hoặc tên lớp và data-thuộc tính), hãy đặt các bộ chọn cho từng thuộc tính cạnh nhau trong chuỗi bộ chọn, không có khoảng cách ngăn cách chúng (vì khoảng trắng chỉ ra các selector hậu duệ ). Ví dụ: để chọn:

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

    sử dụng chuỗi truy vấn .foo.bar. Chọn

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

    sử dụng chuỗi truy vấn .foo[data-bar="someData"]. Để chọn như <span>sau:

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

    sử dụng div.parent > span[data-username="bob"].

  • Viết hoa và đánh vần không thành vấn đề cho tất cả những điều trên. Nếu viết hoa là khác nhau, hoặc chính tả khác nhau, phần tử sẽ không được chọn:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • Bạn cũng cần đảm bảo các phương thức có cách viết hoa và đánh vần đúng. Sử dụng một trong:

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

    Bất kỳ chính tả hoặc viết hoa khác sẽ không hoạt động. Ví dụ, document.getElementByClassNamesẽ ném một lỗi.

  • Hãy chắc chắn rằng bạn truyền một chuỗi cho các phương thức chọn này. Nếu bạn vượt qua một cái gì đó không phải là một chuỗi querySelector, getElementById, vv, nó gần như chắc chắn sẽ không làm việc.


0

Khi tôi thử mã của bạn, nó đã hoạt động.

Lý do duy nhất khiến sự kiện của bạn không hoạt động, có thể là DOM của bạn chưa sẵn sàng và nút của bạn với id "event-btn" chưa sẵn sàng. Và javascript của bạn đã được thực thi và cố gắng liên kết sự kiện với phần tử đó.

Trước khi sử dụng phần tử DOM để liên kết, phần tử đó phải sẵn sàng. Có nhiều lựa chọn để làm điều đó.

Option1: Bạn có thể di chuyển mã ràng buộc sự kiện của bạn trong tài liệu sự kiện đã sẵn sàng. Giống:

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

Option2: Bạn có thể sử dụng thời gian chờ sự kiện, để ràng buộc bị trì hoãn trong vài giây. giống:

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

Tùy chọn 3: di chuyển javascript của bạn đến cuối trang.

Tôi hy vọng cái này sẽ giúp bạn.


@ Willdomybest18, vui lòng kiểm tra câu trả lời này
Jatinder Kumar

-1

vấn đề là phần tử dom 'speclist' không được tạo tại thời điểm mã javascript đang được thực thi. Vì vậy, tôi đặt mã javascript bên trong một hàm và gọi hàm đó trong sự kiện body onload.

function do_this_first(){
   //appending code
}

<body onload="do_this_first()">
</body>

-1

Giải pháp của tôi là không sử dụng id của phần tử neo : <a id='star_wars'>Place to jump to</a>. Rõ ràng blazor và các khuôn khổ spa khác có vấn đề nhảy vào neo trên cùng một trang. Để có được xung quanh mà tôi đã phải sử dụng document.getElementById('star_wars'). Tuy nhiên, điều này không hoạt động cho đến khi tôi đặt id trong phần tử đoạn văn : <p id='star_wars'>Some paragraph<p>.

Ví dụ sử dụng 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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.