Чому jQuery або метод DOM, такий як getElementById, не знаходить елемент?


483

Які можливі причини document.getElementById, $("#id")або будь-який інший метод DOM / селектор jQuery не знайшов елементів?

Приклад проблем:

  • jQuery мовчки не вдається зв’язати обробник подій
  • JQuery "гетерні" методи ( .val(), .html(), .text()) поверненняundefined
  • Стандартний метод повернення DOM, що nullпризводить до будь-якої з декількох помилок:

Uncaught TypeError: Неможливо встановити властивість '...' of null Uncaught TypeError: Неможливо прочитати властивість '...' of null

Найпоширеніші форми:

Uncaught TypeError: Неможливо встановити властивість 'onclick' null

Uncaught TypeError: Неможливо прочитати властивість 'addEventListener' з null

Uncaught TypeError: Неможливо прочитати властивість 'style' з null


32
Багато питань задаються питанням, чому певний елемент DOM не знайдено, і причина часто в тому, що код JavaScript ставиться перед елементом DOM. Це покликане стати канонічною відповіддю на подібний тип питань. Це вікі спільноти, тому не соромтеся вдосконалювати її .
Фелікс Клінг

Відповіді:


481

Елемент, який ви намагалися знайти, не був у DOM, коли ваш сценарій запускався.

Положення вашого DOM-сценарію може мати глибокий вплив на його поведінку. Браузери аналізують HTML-документи зверху вниз. Елементи додаються до DOM, а сценарії (як правило) виконуються у міру їх виникнення. Це означає, що порядок має значення. Зазвичай сценарії не можуть знайти елементи, які з’являться пізніше у розмітці, оскільки їх ще потрібно додати до DOM.

Розглянемо наступну розмітку; Сценарій №1 не знайде <div>успіх сценарію №2:

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

Отже, що вам робити? У вас є кілька варіантів:


Варіант 1. Переміщення сценарію

Перемістіть сценарій далі вниз по сторінці, безпосередньо перед закриваючим тегом тіла. Організовано таким чином, решта документа аналізується перед виконанням сценарію:

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

Примітка. Розміщення сценаріїв внизу, як правило, вважається найкращою практикою .


Варіант 2: jQuery's ready()

Відкладіть свій сценарій до повного розбору DOM, використовуючи :$(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>

Примітка: Ви можете просто зв'язуватися з DOMContentLoadedабо , але кожен з них має свої застереження. jQuery's пропонує гібридне рішення.window.onloadready()


Варіант 3: Делегація подій

Делеговані події мають перевагу в тому, що вони можуть обробляти події з нащадкових елементів, які додаються до документа пізніше.

Коли елемент породжує подію (за умови, що це повальна подія, і ніщо не зупиняє її розповсюдження), кожен батько в родині цього елемента також отримує подію. Це дозволяє нам приєднати обробник до вже наявного елемента та вибірки подій, коли вони спливають від його нащадків ... навіть до тих, що додаються після того, як обробник додається. Все, що нам потрібно зробити, - це перевірити подію, щоб побачити, чи був піднятий потрібний елемент, і, якщо так, запустити наш код.

jQuery on()виконує цю логіку для нас. Ми просто надаємо ім'я події, селектор для бажаного нащадка та обробник подій:

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

Примітка: Зазвичай ця схема зарезервована для елементів, які не існували під час завантаження або щоб уникнути приєднання великої кількості обробників. Варто також зазначити, що, хоча я додав обробник до document(для демонстративних цілей), ви повинні вибрати найближчого надійного предка.


Варіант 4: deferАтрибут

Використовуйте deferатрибут <script>.

[ defer, атрибут Boolean,] встановлюється, щоб вказати браузеру, що сценарій повинен бути виконаний після розбору документа, але перед його запуском DOMContentLoaded.

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

Для довідки, ось код із цього зовнішнього скрипту :

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

Примітка: deferАтрибут, безумовно, здається магічною кулею, але важливо знати про застереження ...
1. deferможе використовуватися лише для зовнішніх скриптів, тобто: тих, що мають srcатрибут.
2. бути в курсі підтримки браузера , тобто: баггічна реалізація в IE <10


Зараз 2020 рік - чи "Розміщення сценаріїв внизу" все ще "вважається найкращою практикою"? Я (сьогодні) поклав усі свої ресурси на <head>та використовую deferсценарії (мені не потрібно підтримувати погані старі несумісні браузери)
Stephen P

Я великий прихильник переходу на сучасні браузери. Однак, ця практика насправді нічого не коштує, і вона просто працює скрізь. З іншого боку, як deferі asyncмає досить широку підтримку -навіть йде до деяких попередніх його версій браузерів. Вони мають додаткову перевагу - чітко, чітко сигналізуючи про передбачувану поведінку. Просто пам’ятайте, що ці атрибути працюють лише для скриптів із зазначенням srcатрибуту, тобто: зовнішніх скриптів. Зважте переваги. Може використовувати обоє? Твій дзвінок.
канон

146

Короткий і простий: оскільки шуканих елементів не існує в документі (поки).


В протягом решти цього відповіді я буду використовувати в getElementByIdякості прикладу, але те ж саме відноситься і до getElementsByTagName, querySelectorі будь-який інший метод , який вибирає DOM - елементів.

Можливі причини

Є дві причини, чому елемент може не існувати:

  1. Елемента з переданим ідентифікатором дійсно не існує в документі. Вам слід двічі перевірити, чи ідентифікатор, який ви передаєте, getElementByIdдійсно відповідає ідентифікатору існуючого елемента в (згенерованому) HTML-коді та чи не ви написали неправильно ідентифікатор (ідентифікатори залежать від регістру !)

    Між іншим, в більшості сучасних браузерів , які реалізують querySelector()і querySelectorAll()методи, CSS-стиль позначення використовується для вилучення елемента в відповідно з його id, наприклад: document.querySelector('#elementID'), на відміну від способу , з допомогою якого елемент витягується його idзаступником document.getElementById('elementID'); у першому #персонаж є істотним, у другому це призведе до того, що елемент не буде вилучений.

  2. Елемент не існує в той момент, коли ви телефонуєте getElementById.

Останній випадок є досить поширеним. Браузери аналізують і обробляють HTML зверху вниз. Це означає, що будь-який виклик елемента DOM, який відбувається до того, як елемент DOM з'явиться в HTML, буде невдалим.

Розглянемо наступний приклад:

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

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

divЗ'являється післяscript . На даний момент скрипт виконується, то елемент не існує ще й getElementByIdповернеться null.

jQuery

Те саме стосується всіх селекторів з jQuery. jQuery не знайде елементів, якщо ви неправильно написали свій селектор або намагаєтесь вибрати їх до того, як вони насправді існують .

Доданий поворот - це коли jQuery не знайдено, тому що ви завантажили сценарій без протоколу і працюєте з файлової системи:

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

цей синтаксис використовується для дозволу завантаження сценарію через HTTPS на сторінку з протоколом https: // і для завантаження версії HTTP на сторінку з протоколом http: //

Це має прикрий побічний ефект від спроб і не вдається завантажити file://somecdn.somewhere.com...


Рішення

Перш ніж здійснити виклик getElementById(або будь-який метод DOM з цього питання), переконайтеся, що елементи, до яких ви хочете отримати доступ, тобто завантажуються DOM.

Це можна забезпечити, просто поставивши JavaScript після відповідного елемента DOM

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

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

в такому випадку ви також можете поставити код безпосередньо перед тегом закриття тіла ( </body>) (всі елементи DOM будуть доступні в момент виконання сценарію).

Інші рішення включають прослуховування подій load [MDN] або DOMContentLoaded [MDN] . У цих випадках неважливо, де в документі ви розміщуєте код JavaScript, вам просто потрібно пам’ятати, щоб увесь код обробки DOM був розміщений в обробниках подій.

Приклад:

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

// or

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

Будь ласка, перегляньте статті на quirksmode.org для отримання додаткової інформації щодо обробки подій та відмінностей у веб-переглядачі.

jQuery

Спочатку переконайтеся, що jQuery завантажений належним чином. Використовуйте інструменти для розробників браузера, щоб дізнатись, чи знайдено файл jQuery та виправити URL-адресу, якщо вона не була (наприклад, додайте http:або https:на початку схему, відрегулюйте шлях тощо).

Прослуховування load/ DOMContentLoaded подій саме те, що робить jQuery з .ready() [docs] . Весь ваш код jQuery, який впливає на елемент DOM, повинен знаходитися всередині цього обробника подій.

Насправді в підручнику jQuery прямо вказано:

Оскільки майже все, що ми робимо, використовуючи jQuery, читає або маніпулює об'єктною моделлю документа (DOM), нам потрібно переконатися, що ми починаємо додавати події тощо, як тільки DOM буде готовий.

Для цього ми реєструємо готовий захід для документа.

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

Крім того, ви можете також використовувати синтаксис скорочень:

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

Обидва рівнозначні.


1
Чи варто змінити останній біт про readyподію, щоб відобразити "новіший" бажаний синтаксис, чи краще залишити його як - це так, щоб він працював у старих версіях?
Тієсон Т.

1
Для JQuery, варто також додавати в якості альтернативи $(window).load()(і , можливо , синтаксичних альтернатив $(document).ready(), $(function(){})це , здається , пов'язані, але це? Чи відчуває трохи по дотичній до точки ви робите.
Девід каже Моніка відновило

@David: Хороший момент .load. Щодо альтернатив синтаксису, то їх можна шукати в документації, але оскільки відповіді повинні бути самостійними, можливо, варто їх додати. Знову ж таки, я впевнений, що на цей конкретний біт про jQuery вже відповіли, і, ймовірно, висвітлено в інших відповідях, а посилання на нього може бути достатньо.
Фелікс Клінг

1
@David: Я мушу переглянути: Очевидно .loadзастаріла: api.jquery.com/load-event . Я не знаю, чи це лише для зображень чи windowдобре.
Фелікс Клінг

1
@David: Не хвилюйтесь :) Навіть якщо ви не впевнені, добре, що ви це згадали. Я, мабуть, додам лише кілька простих фрагментів коду, щоб показати використання. Дякуємо за ваш внесок!
Фелікс Клінг

15

Причини, чому селектори на основі ідентифікації не працюють

  1. Елемент / DOM із вказаним ідентифікатором ще не існує.
  2. Елемент існує, але він не реєструється в DOM [у разі HTML-вузлів, що динамічно додаються з відповідей Ajax].
  3. Присутні більше одного елемента з однаковим ідентифікатором, що викликає конфлікт.

Рішення

  1. Спробуйте отримати доступ до елемента після його декларування або використовуйте подібні речі $(document).ready();

  2. Для елементів, що надходять з відповідей Ajax, використовуйте .bind()метод jQuery. Старіші версії jQuery мали .live()те саме.

  3. Використовуйте інструменти [наприклад, плагін веб-розробника для браузерів], щоб знайти дублікати ідентифікаторів і видалити їх.


13

Як зазначав @FelixKling, найбільш вірогідний сценарій полягає в тому, що вузли, які ви шукаєте, не існують (поки що).

Однак сучасна практика розробки часто може маніпулювати елементами документа за межами дерева документів або за допомогою DocumentFragments або просто від'єднувати / повторно приєднувати поточні елементи безпосередньо. Такі методи можуть використовуватися як частина шаблонів JavaScript або для уникнення надмірних операцій перефарбовування / поповнення, в той час як елементи, про які йдеться, сильно змінюються.

Аналогічно, новий функціонал "Shadow DOM", який розповсюджується в сучасних браузерах, дозволяє елементам бути частиною документа, але не мати можливості запиту за допомогою document.getElementById та всіх його методів братів (querySelector тощо). Це робиться для інкапсуляції функціональності та спеціальної прихованості.

Знову ж таки, швидше за все, найімовірніше, що шуканий елемент просто не є (ще) в документі, і ви повинні робити так, як пропонує Фелікс. Однак ви також повинні знати, що це все частіше не є єдиною причиною того, що елемент може бути незмінним (тимчасово чи постійно).


13

Якщо елемент, до якого ви намагаєтеся отримати доступ, знаходиться всередині iframeта ви намагаєтесь отримати доступ до нього поза контекстом цього, iframeце також призведе до його відмови.

Якщо ви хочете отримати елемент в iframe, ви можете дізнатися, як тут .


7

Якщо наказ про виконання сценарію не є проблемою, інша можлива причина проблеми полягає в тому, що елемент не вибраний належним чином:

  • getElementByIdвимагає, щоб переданий рядок був ідентичним дослідним , і нічого іншого. Якщо ви будете префіксувати переданий рядок з a #, а ідентифікатор не починається з a #, нічого не буде обрано:

    <div id="foo"></div>
    // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
  • Аналогічно, для getElementsByClassName, не префіксуйте переданий рядок з .:

    <div class="bar"></div>
    // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
  • За допомогою querySelector, querySelectorAll та jQuery, щоб відповідати елементу з конкретним іменем класу, поставте .безпосередньо перед класом. Аналогічно, щоб зіставити елемент з певним ідентифікатором, поставте #безпосередньо перед ідентифікатором:

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

    Тут правила в більшості випадків ідентичні правилам для селекторів CSS, і їх можна детально ознайомити тут .

  • Щоб відповідати елементу, який має два або більше атрибутів (наприклад, два імені класу, або ім’я класу та data-атрибут), виберіть селектори для кожного атрибута поруч один з одним у рядку селектора, не розділяючи їх пробілом (оскільки пробіл вказує селектор нащадок ). Наприклад, щоб вибрати:

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

    використовувати рядок запиту .foo.bar. Щоб вибрати

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

    використовувати рядок запиту .foo[data-bar="someData"]. Щоб вибрати <span>нижче:

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

    використання div.parent > span[data-username="bob"].

  • Велика література та правопис мають значення для всього вищезазначеного. Якщо величина з великої літери або написання відрізняється, елемент не буде обраний:

    <div class="result"></div>
    // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
  • Вам також потрібно переконатися, що методи мають правильну написання великої літери та написання. Скористайтеся одним із:

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

    Будь-яке інше написання чи написання з великої літери не працюватиме. Наприклад, document.getElementByClassNameвикине помилку.

  • Переконайтеся, що ви передали рядок цим селекторним методам. Якщо ви передасте щось, що не є рядком querySelector, getElementByIdі т.д., це майже напевно не вийде.


0

Коли я спробував ваш код, він спрацював.

Єдиною причиною того, що ваша подія не працює, може бути те, що ваш DOM не був готовий і ваша кнопка з id "event-btn" ще не була готова. І ваш JavaScript виконується і намагається зв’язати подію з цим елементом.

Перш ніж використовувати елемент DOM для прив’язки, цей елемент повинен бути готовим. Є багато варіантів для цього.

Варіант1: Ви можете перемістити код прив'язки події в рамках події, готової до документа. Подібно до:

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

Варіант 2: Ви можете використовувати подію тайм-ауту, так що прив'язка затягується на кілька секунд. подібно до:

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

Варіант3: перемістіть ваш javascript включно внизу сторінки.

Я сподіваюся, що це вам допоможе.


@ Willdomybest18, будь ласка, перевірте цю відповідь
Джатіндер Кумар

-1

проблема полягає в тому, що елемент "speclist" елемента dom не створюється під час виконання коду javascript. Тому я поставив код JavaScript всередині функції і назвав цю функцію на події завантаження тіла.

function do_this_first(){
   //appending code
}

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

-1

Моє рішення було не використовувати ідентифікатор елемента якоря: <a id='star_wars'>Place to jump to</a>. Судячи з усього, у бризових та інших спа-систем є проблеми зі стрибками на якір на одній сторінці. Щоб обійти те, що довелося використовувати document.getElementById('star_wars'). Однак це не спрацювало , поки я не поставив ідентифікатор в елементі абзацу замість: <p id='star_wars'>Some paragraph<p>.

Приклад використання завантажувальної програми:

<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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.