Почему jQuery или метод DOM, такой как getElementById, не находят элемент?

483

Каковы возможные причины того document.getElementById, что $("#id")любой другой селектор метода DOM / jQuery не находит элементы?

Примеры проблем включают в себя:

  • JQuery молча не удается связать обработчик событий
  • JQuery "геттерные" методы ( .val(), .html(), .text()) возвращениеundefined
  • Стандартный метод DOM, возвращающий в nullрезультате любую из нескольких ошибок:

Uncaught TypeError: Невозможно установить свойство '...' с нулевым значением Uncaught TypeError: Невозможно прочитать свойство '...' с нулевым значением

Наиболее распространенные формы:

Uncaught TypeError: Невозможно установить свойство 'onclick' для null

Uncaught TypeError: Невозможно прочитать свойство 'addEventListener' с нулевым значением

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. Организованный таким образом, остальная часть документа анализируется перед выполнением вашего скрипта:

<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 предлагает гибридное решение.window.onloadready()


Вариант 3: делегирование событий

Преимущество делегированных событий заключается в том, что они могут обрабатывать события от элементов-потомков, которые добавляются в документ позднее.

Когда элемент вызывает событие (при условии, что это пузырьковое событие, и ничто не останавливает его распространение), каждый родительский элемент в происхождении этого элемента также получает событие. Это позволяет нам присоединять обработчик к существующему элементу и сэмплировать события, когда они всплывают от его потомков ... даже тех, которые добавлены после присоединения обработчика. Все, что нам нужно сделать, это проверить событие, чтобы увидеть, было ли оно вызвано нужным элементом, и, если это так, запустить наш код.

JQuery's 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, логический атрибут] устанавливается для указания браузеру, что сценарий должен выполняться после анализа документа, но до его запуска 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(мне не нужно поддерживать старые, несовместимые браузеры)
Стивен П.
Я большой сторонник перехода на современные браузеры. Тем не менее, эта практика на самом деле ничего не стоит мне, и она просто работает везде. С другой стороны, и те, deferи другие asyncимеют довольно широкую поддержку - даже если вернуться к некоторым более старым версиям браузера. У них есть дополнительное преимущество: они четко и явно сигнализируют о предполагаемом поведении. Просто помните, что эти атрибуты работают только для сценариев, указывающих srcатрибут, то есть для внешних сценариев. Взвесьте преимущества. Может использовать оба? Ваш звонок.
канон
146

Коротко и просто: потому что искомые элементы не существуют в документе (пока).


В течение оставшейся части этого ответа я буду использовать в getElementByIdкачестве примера, но то же самое относится и к getElementsByTagName, querySelectorи любой другой метод , который выбирает DOM - элементов.

Возможные причины

Есть две причины, почему элемент может не существовать:

  1. Элемент с переданным идентификатором действительно не существует в документе. Вы должны дважды проверить, что идентификатор, который вы передаете, getElementByIdдействительно совпадает с идентификатором существующего элемента в (сгенерированном) HTML, и что вы не ошиблись идентификатором (идентификаторы чувствительны к регистру !).

    Между прочим, в большинстве современных браузеров , которые реализуют querySelector()и querySelectorAll()методы, нотация в стиле CSS используется для извлечения элемента его id, например: document.querySelector('#elementID')в отличие от метода, которым элемент извлекается его idunder 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 ( </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
});

Оба эквивалентны.

Felix Kling
источник
1
Стоит ли вносить последние изменения в readyсобытие, чтобы отразить «более новый» предпочтительный синтаксис, или лучше оставить его как есть, чтобы оно работало в более старых версиях?
Tieson T.
1
Для JQuery, стоит также добавлять в качестве альтернативы $(window).load()(и , возможно , синтаксических альтернатив $(document).ready(), $(function(){})это , кажется , связаны, но это? Чувствует немного по касательной к точке вы делаете.
Дэвид говорит Моника восстановило
@ Дэвид: Хороший вопрос с .load. Что касается синтаксических альтернатив, их можно найти в документации, но поскольку ответы должны быть самодостаточными, возможно, их стоит добавить. Опять же, я почти уверен, что на этот конкретный бит о jQuery уже дан ответ, и, вероятно, он описан в других ответах, и ссылки на него могут быть достаточными.
Феликс Клинг
1
@ Дэвид: Я должен пересмотреть: Очевидно, .loadне рекомендуется: api.jquery.com/load-event . Я не знаю, только для изображений или windowтак же.
Феликс Клинг
1
@ Дэвид: Не беспокойся :) Даже если ты не уверен, хорошо, что ты упомянул об этом. Я, вероятно, добавлю несколько простых фрагментов кода, чтобы показать их использование. Спасибо за ваш вклад!
Феликс Клинг
15

Причины, по которым селекторы на основе идентификаторов не работают

  1. Элемент / DOM с указанным идентификатором еще не существует.
  2. Элемент существует, но он не зарегистрирован в DOM [в случае HTML-узлов, добавляемых динамически из ответов Ajax].
  3. Присутствует более одного элемента с одинаковым идентификатором, что вызывает конфликт.

Решения

  1. Попробуйте получить доступ к элементу после его объявления или, в качестве альтернативы, использовать такие вещи, как $(document).ready();

  2. Для элементов, полученных из ответов Ajax, используйте .bind()метод jQuery. Более старые версии jQuery были .live()для того же.

  3. Используйте инструменты [например, плагин веб-разработчика для браузеров], чтобы найти дубликаты идентификаторов и удалить их.

Sumit
источник
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и т.д., это почти наверняка не будет работать.

CertainPerformance
источник
0

Когда я попробовал твой код, он работал.

Единственная причина, по которой ваше событие не работает, может заключаться в том, что ваш DOM не был готов, а ваша кнопка с идентификатором "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

проблема в том, что элемент dom 'speclist' не создается во время выполнения кода javascript. Поэтому я поместил код javascript в функцию и вызвал эту функцию для события onload тела.

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>
goku_da_master
источник