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

372

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

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

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

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

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

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

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

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

  • 28
    Многие вопросы задаются о том, почему определенный элемент DOM не найден, и причина часто в том, что код JavaScript помещается перед элементом DOM. Это должно быть каноническим ответом на подобные вопросы. Это вики сообщества, поэтому, пожалуйста, не стесняйтесь ее улучшать .
Теги:
dom

8 ответов

405
Лучший ответ

Элемент, который вы пытались найти, не был в DOM, когда выполнялся ваш script.

Положение вашего DOM-зависимого script может оказать глубокое влияние на его поведение. Браузеры анализируют HTML-документы сверху донизу. Элементы добавляются в DOM, и сценарии выполняются (как правило), когда они встречаются. Это означает, что порядок имеет значение. Обычно сценарии не могут найти элементы, которые появляются позже в разметке, потому что эти элементы еще не добавлены в DOM.

Рассмотрим следующую разметку; script # 1 не удается найти <div>, а script # 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. Переместите script

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

<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 ready()

Отложите script до полного анализа DOM, используя ready():

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(document).ready(function() {
    $("#test").click(function() {
      console.log("clicked: %o", this);
    });
  });
</script>
<button id="test">click me</button>

Примечание. Вы можете просто привязать к DOMContentLoaded или window.onload, но у каждого есть свои оговорки. jQuery ready() предоставляет гибридное решение.


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

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

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

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 attribute,] установлен для указания браузеру, что script предназначен для выполнения после того, как документ был проанализирован.

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

Для справки, здесь код из внешний script:

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

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

  • 0
    Ох, я пропустил это, поскольку это было мелким шрифтом под заголовком jQuery :)
135

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


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

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

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

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

Рассмотрим следующий пример:

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

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

После script появляется div. В настоящий момент выполняется script, этот элемент еще не существует, а getElementById возвращает null.

JQuery

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

Добавленный твист - это когда jQuery не найден, потому что вы загрузили script без протокола и запускаетесь из файловой системы:

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

этот синтаксис используется, чтобы позволить 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 будут доступны в момент выполнения script).

Другие решения включают прослушивание 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(){}) ? Кажется связанным, но это чувствует себя слегка касательным к точке, которую вы делаете.
Показать ещё 12 комментариев
12

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

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

Решения

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

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

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

10

Если элемент, к которому вы пытаетесь получить доступ, находится внутри iframe, и вы пытаетесь получить к нему доступ вне контекста iframe, это также приведет к сбою.

Если вы хотите получить элемент в iframe, вы можете узнать, как здесь.

10

Как отметил @FelixKling, наиболее вероятным сценарием является то, что узлы, которые вы ищете, пока не существуют.

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

Аналогично, новая функциональность "Теневой DOM", развернутая в современных браузерах, позволяет элементам быть частью документа, но не может быть выполнена с помощью document.getElementById и всех его методов sibling (querySelector и т.д.). Это делается для инкапсуляции функциональных возможностей и скрыть их.

Опять же, скорее всего, элемент, который вы ищете, просто не является (пока) в документе, и вы должны делать то, что предлагает Феликс. Тем не менее, вы также должны знать, что это все чаще является не единственной причиной того, что элемент может быть неоправданным (временно или постоянно).

0

есть и другая ситуация, которая может вызвать такую ошибку. Представьте, что у вас есть этот код:

<script>
    $(document).ready(function() {
        $('body').append('<div id="my-element"></div>');
    });
</script>

этот код создает элемент #my-element внутри тега body.

и после этого у нас может быть следующий код:

<script>
    console.log($('#my-element'));
</script>

второй код запускается до первого! потому что первый запускается после полной загрузки документа. и это может вызвать ошибку.

-1

Попробуйте поместить document.getElementById в setTimeout()

Например

setTimeout(function(){
    console.log(document.getElementById('whatever'));
}, 100);

Если это сработает, тогда это просто проблема времени.

  • 1
    Это ненадежно. Это не «проблема времени». Смотрите другие ответы здесь для реальных решений и объяснений.
-6

Я просто боролся с этим и делился, если это было полезно.

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

В конце я переписал строку кода. Я не ошиблись, но думаю, что я использовал буквы "ID". Теперь он отлично работает.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню