Каковы возможные причины для document.getElementById
, $("#id")
или любого другого селектора метода DOM/jQuery, не находящего элементы?
Примеры проблем включают в себя:
.val()
, .html()
, .text()
) возвращают undefined
Стандартный метод DOM, возвращающий null
приводящий к любой из нескольких ошибок:
Uncaught TypeError: Невозможно установить свойство '...' с нулевым значением Uncaught TypeError: Невозможно установить свойство '...' с нулевым значением
Наиболее распространенные формы:
Uncaught TypeError: Невозможно установить свойство 'onclick' для null
Uncaught TypeError: Невозможно прочитать свойство 'addEventListener' с нулевым значением
Uncaught TypeError: Невозможно прочитать свойство 'style' из null
Элемент, который вы пытались найти, не был в 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>
Итак, что вы должны делать? У вас есть несколько вариантов:
Переместите ваш 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 -->
Примечание. Размещение сценариев внизу обычно считается лучшей практикой.
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()
предоставляет гибридное решение.
Делегированные события имеют то преимущество, что они могут обрабатывать события из элементов-потомков, которые будут добавлены в документ позже.
Когда элемент вызывает событие (при условии, что это событие 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
(для демонстрационных целей), вы должны выбрать ближайшего надежного предка.
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суб >
Короче и просто:. Поскольку элементы, которые вы ищете, не существуют в документе (пока).
В оставшейся части этого ответа я буду использовать getElementById
в качестве примера, но то же самое относится к getElementsByTagName
, querySelector
и любой другой метод DOM, который выбирает элементы.
Возможные причины
Есть две причины, по которым элемент может не существовать:
Элемент с переданным идентификатором действительно не существует в документе. Вы должны дважды проверить, что идентификатор, который вы передаете в getElementById
, действительно соответствует идентификатору существующего элемента в (сгенерированном) HTML и что вы не ошибочно написали идентификатор (идентификаторы чувствительны к регистру!).
Кстати, в большинстве современных браузеров , в которых реализованы методы querySelector()
и querySelectorAll()
, для извлечения используется стиль CSS-стиля элемент его id
, например: document.querySelector('#elementID')
, в отличие от метода, с помощью которого элемент извлекается его id
под document.getElementById('elementID')
; в первом символе #
необходимо, во втором это приведет к тому, что элемент не будет извлечен.
Элемент не существует в тот момент, когда вы вызываете getElementById
.
Последний случай довольно распространен. Браузеры анализируют и обрабатывают 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
});
Оба эквивалентны.
ready
событие, чтобы оно отражало «более новый» предпочтительный синтаксис, или лучше оставить его как есть, чтобы оно работало в более старых версиях?
$(window).load()
(и, возможно, синтаксические альтернативы $(document).ready()
, $(function(){})
? Кажется связанным, но это чувствует себя слегка касательным к точке, которую вы делаете.
Причины, по которым селекторы на основе id не работают
Решения
Попробуйте получить доступ к элементу после его объявления или, альтернативно, использовать такие вещи, как $(document).ready();
Для элементов, исходящих из ответов Ajax, используйте метод .bind()
для jQuery. Более старые версии jQuery имели .live()
для того же самого.
Используйте инструменты [например, плагин webdeveloper для браузеров], чтобы найти дубликаты идентификаторов и удалить их.
Если элемент, к которому вы пытаетесь получить доступ, находится внутри iframe
, и вы пытаетесь получить к нему доступ вне контекста iframe
, это также приведет к сбою.
Если вы хотите получить элемент в iframe, вы можете узнать, как здесь.
Как отметил @FelixKling, наиболее вероятным сценарием является то, что узлы, которые вы ищете, пока не существуют.
Однако современные методы разработки часто могут манипулировать элементами документа за пределами дерева документов либо с помощью DocumentFragments, либо просто отделять/повторно присоединять текущие элементы напрямую. Такие методы могут использоваться как часть шаблонов JavaScript или избегать чрезмерных операций перерисовки/переплавки, в то время как элементы, о которых идет речь, сильно изменены.
Аналогично, новая функциональность "Теневой DOM", развернутая в современных браузерах, позволяет элементам быть частью документа, но не может быть выполнена с помощью document.getElementById и всех его методов sibling (querySelector и т.д.). Это делается для инкапсуляции функциональных возможностей и скрыть их.
Опять же, скорее всего, элемент, который вы ищете, просто не является (пока) в документе, и вы должны делать то, что предлагает Феликс. Тем не менее, вы также должны знать, что это все чаще является не единственной причиной того, что элемент может быть неоправданным (временно или постоянно).
есть и другая ситуация, которая может вызвать такую ошибку. Представьте, что у вас есть этот код:
<script>
$(document).ready(function() {
$('body').append('<div id="my-element"></div>');
});
</script>
этот код создает элемент #my-element
внутри тега body.
и после этого у нас может быть следующий код:
<script>
console.log($('#my-element'));
</script>
второй код запускается до первого! потому что первый запускается после полной загрузки документа. и это может вызвать ошибку.
Попробуйте поместить document.getElementById
в setTimeout()
Например
setTimeout(function(){
console.log(document.getElementById('whatever'));
}, 100);
Если это сработает, тогда это просто проблема времени.
Я просто боролся с этим и делился, если это было полезно.
Даже когда я использовал отладчик и обнаружил, что элементы уже существуют во время выполнения, не было видимой причины, по которой консоль регистрировалась (и упала) с помощью undefined.
В конце я переписал строку кода. Я не ошиблись, но думаю, что я использовал буквы "ID". Теперь он отлично работает.