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

Dec 25 2012

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 trình xử lý sự kiện
  • jQuery "getter" phương pháp ( .val(), .html(), .text()) trở vềundefined
  • Phương thức DOM tiêu chuẩn trả về nulldẫn đến bất kỳ lỗi nào trong số một số lỗi:

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

Trả lời

506 canon Jan 04 2012 at 01:06

Phần 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 vào 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ú pháp 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 (nói chung) được thực thi khi chúng gặp phải. Điều này có nghĩa là thứ tự quan trọng. 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 này 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 đánh dấu sau đây; script # 1 không tìm thấy <div>script trong khi script # 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 số tùy 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 dưới trang, ngay trước thẻ đóng nội dung. Được sắp xếp theo cách 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 tập lệnh ở dưới cùng thường được coi là phương pháp hay nhất .


Tùy chọn 2: jQuery's 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, bằng cách 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 thứ đều có những lưu ý. jQuery's cung cấp một giải pháp kết hợp.window.onloadready()


Tùy chọn 3: Ủy quyề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ử con được thêm vào tài liệu sau đó.

Khi một phần tử tạo ra một sự kiện (với điều kiện đó là một sự kiện sôi sục và không có gì ngăn chặn sự lan truyền của nó), thì 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 tôi đính kèm 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ừ các phần tử con của nó ... ngay cả những người đượ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 nâng lên bởi phần tử mong muốn hay không và nếu có, hãy chạy mã của chúng tôi.

jQuery on()thực hiện logic đó cho chúng tôi. Chúng tôi chỉ cung cấp tên sự kiện, bộ chọn cho phần tử con 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 tại thời điểm tải hoặc để tránh đính kèm một lượng lớn trình xử lý. Cũng cần chỉ ra rằng trong khi tôi đã gắn một trình xử lý vào document(cho mục đích minh họa), 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 deferthuộc tính của <script>.

[ defer, thuộc tính Boolean,] được đặt để chỉ ra cho trình duyệt biết rằng tập lệnh được thực thi sau khi tài liệu đã được phân tích cú pháp, nhưng trước khi kích hoạt 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 ý: deferThuộc tính chắc chắn có vẻ giống như một viên đạn ma thuật nhưng điều quan trọng là phải lưu ý ...
1. deferchỉ có thể được sử dụng cho các tập lệnh bên ngoài, tức là: những tập lệnh có một srcthuộc tính.
2. lưu ý đến sự hỗ trợ của trình duyệt , tức là: triển khai lỗi trong IE <10

154 FelixKling Dec 25 2012 at 15:26

Ngắn gọn và đơn giản: Bởi vì các phần 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 phần tử có thể không tồn tại:

  1. Một phần tử có ID đã chuyển 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 phần tử hiện có trong HTML (được tạo) hay không và bạn có viết sai chính tả ID không (ID có phân biệt chữ hoa chữ thường !).

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

  2. Phần tử không tồn tại tại thời điểm bạn gọi getElementById.

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

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ả bộ chọn của mình hoặc bạn đang cố gắng chọn chúng trước khi chúng thực sự tồn tại .

Một vấn đề khác 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 tập lệnh tải qua HTTPS trên trang có giao thức https: // và tải phiên bản HTTP trên trang có giao thức http: //

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


Các giải pháp

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

Đ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ẻ đóng nội dung ( </body>) (tất cả các phần tử 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 lắng nghe các sự kiện load [MDN] hoặc DOMContentLoaded [MDN] . Trong những trường hợp này, không quan trọng bạn đặt mã JavaScript ở đâu trong tài liệu, 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
});

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 của trình duyệt.

jQuery

Trước tiên, hãy đảm bảo rằng jQuery được tải đúng cách. Sử dụng các công cụ dành cho nhà phát triển của trình duyệt để tìm xem tệp jQuery có được tìm thấy hay không và sửa URL nếu nó không phải (ví dụ: thêm http:hoặc https:lược đồ ở đầu, điều chỉnh đường dẫn, v.v.)

Lắng 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ả mã jQuery của bạn có ảnh hưởng đến phần tử DOM phải nằm trong trình xử lý sự kiện đó.

Trên thực tế, hướng dẫn jQuery tuyên bố rõ ràng:

Như hầu hết mọi thứ chúng ta làm khi sử dụng jQuery đều đọ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, v.v. 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 viết tắt:

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

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

16 GeorgeMulligan Jan 06 2016 at 06:34

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

Nếu bạn muốn lấy một phần tử trong iframe, bạn có thể tìm hiểu cách thực hiện tại đây .

16 sumit Jan 07 2014 at 19:12

Lý do tại sao bộ chọn dựa trên id không hoạt động

  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 thêm động từ các phản hồi Ajax].
  3. Có nhiều phần tử có cùng id đang gây ra 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ác công cụ như $(document).ready();

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

  3. Sử dụng các công cụ [ví dụ: plugin trình phát triển web cho trình duyệt] để tìm id trùng lặp và xóa chúng.

13 NathanBubna Dec 07 2013 at 00:29

Như @FelixKling đã chỉ ra, trường hợp có thể xảy ra 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 DocumentFragment hoặc đơn giản là tách / gắn lại trực tiếp các phần tử hiện tại. Các kỹ thuật như vậy có thể được sử dụng như một phần của quá trình tạo khuôn mẫu JavaScript hoặc để tránh các hoạt động tô lại / chỉnh lại quá mức trong khi các phần tử được đề cập đang bị thay đổi nhiều.

Tương tự, chức năng "Shadow DOM" mới đang được triển khai trên các trình duyệt hiện đại cho phép các phần tử trở thành một phần của tài liệu, nhưng không thể truy vấn bởi document.getElementById và tất cả các phương thức anh 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ể phần tử bạn đang tìm kiếm đơn giản là chưa (chưa) có trong tài liệu và bạn nên làm như Felix gợi ý. 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 phần tử có thể không thể tìm được (tạm thời hoặc vĩnh viễn).

8 CertainPerformance May 10 2019 at 16:29

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 sự cố là phần tử không được chọn đúng cách:

  • getElementByIdyêu cầu chuỗi đã truyền phải là ID nguyên văn và không có gì khác. Nếu bạn đặt trước 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ự, đối với getElementsByClassName, không đặt tiền tố chuỗi đã truyền bằng .:

      <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ử có tên lớp cụ thể, hãy đặt .trực tiếp trước lớp đó. Tương tự, để đối sánh một phần tử với một ID cụ thể, hãy đặt #ngay 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 với các quy tắc dành cho bộ chọn CSS và có thể xem chi tiết tại đây .

  • Để so khớp 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à một data-thuộc tính), hãy đặt các bộ chọn cho từng thuộc tính bên cạnh nhau trong chuỗi bộ chọn, không có khoảng trắng ngăn cách chúng (vì dấu cách cho biết 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 <span>bên dưới:

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

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

  • Viết hoa và chính tả thực sự quan trọng đối với tất cả những điều trên. Nếu cách viết hoa khác hoặc cách viết khác, 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à chính tả phù hợp. Sử dụng một trong số:

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

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

  • Đảm bảo rằng bạn chuyển một chuỗi cho các phương thức bộ 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.

  • Nếu các thuộc tính HTML trên các phần tử bạn muốn chọn được bao quanh bởi dấu ngoặc kép thì chúng phải là dấu ngoặc kép đơn giản (đơn hoặc kép); dấu ngoặc kép giống như hoặc sẽ không hoạt động nếu bạn đang cố gắng chọn theo ID, lớp hoặc thuộc tính.

JatinderKumar Apr 18 2020 at 15:21

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ó id "event-btn" của bạn 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 đó.

Tùy chọn 1: Bạn có thể di chuyển mã ràng buộc sự kiện của mình trong sự kiện sẵn sàng tài liệu. Giống:

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

Tùy chọn 2: Bạn có thể sử dụng sự kiện thời gian chờ, do đó ràng buộc sẽ 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 bao gồm javascript của bạn xuống cuối trang của bạn.

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