#Что означает ошибка
Cannot read properties of null
Метод getElementById('btn') возвращает null, если элемента с таким id нет в документе в момент вызова. Затем null.addEventListener(...) вызывает TypeError. Это одна из самых частых ошибок JavaScript, и причина всегда в одном из трёх: скрипт выполняется слишком рано, селектор не совпадает, либо элемент создаётся динамически позже.
#Причины и решения
| Возможная причина | Как узнать | Что делать |
|---|---|---|
Скрипт выполняется до загрузки DOMТег script стоит в head и подключён без атрибута defer. | Ошибка при открытии страницы. | Добавьте defer или переместите скрипт вниз. |
Неверный селекторОпечатка в id/классе, либо # и . перепутаны. | console.log(el) выводит null. | Проверьте имя в HTML. |
| Элемент создаётся позжеЭлемент появляется после AJAX, рендера фреймворка. | В момент вызова его ещё нет. | Используйте делегирование событий. |
| Другая страницаОбщий скрипт запущен на странице без этого элемента. | Ошибка только на некоторых страницах. | Проверяйте наличие элемента. |
#Пошаговое решение
- Найдите строку ошибкиОткройте проблемную страницу в браузере, нажмите F12 или Ctrl+Shift+J (обычно Chrome/Edge) → вкладка Console и найдите сообщение
Cannot read properties of null (reading 'addEventListener'). Нажмите по ссылке с именем скрипта и строкой: браузер перейдёт на вкладку Sources к месту вызова. Посмотрите, какая переменная слева от.addEventListenerравнаnull. Запомните, на какой странице и после какого действия ошибка появляется. Если код принадлежит стороннему расширению, сначала проверьте страницу в приватном окне без расширений. - Выведите элементВо вкладке Console выполните проверку без изменения страницы, например
document.querySelector('#btn'), где#btnзамените реальным селектором из проблемной строки. Если выводnull, элемент не найден в текущем документе; если показан HTML-элемент, причина может быть в моменте выполнения или другом контексте. ДляgetElementById('btn')передавайте имя без#. Если элемент находится внутри iframe или теневого DOM, обычныйdocument.querySelectorиз основного документа может его не видеть — уточните контекст, не создавайте дубликат кнопки. - Проверьте HTMLОткройте DevTools → Elements (или «Элементы»), нажмите Ctrl+F и найдите
id="btn"либо нужный класс. Сравните регистр букв, точное имя и отсутствие лишних пробелов. Проверьте, не используют ли две кнопки одинаковыйid— это нарушает корректную идентификацию элемента. Если HTML создаётся шаблонизатором, отредактируйте исходный шаблон или компонент, а не только видимый HTML в DevTools. После сохранения обновите страницу без кэша и вновь выполните селектор из предыдущего шага. - Подключите скрипт правильноЕсли элемент существует, но скрипт исполняется до разбора HTML, откройте исходный HTML-файл и найдите тег
<script src="app.js">. Для внешнего обычного скрипта можно добавитьdefer, чтобы он выполнялся после разбора HTML:<script src="app.js" defer></script>. Альтернатива — обернуть регистрацию обработчиков вdocument.addEventListener('DOMContentLoaded', () => { ... }). Не используйте оба способа без необходимости и учтите, что модулиtype="module"уже откладывают выполнение по умолчанию. Перезагрузите страницу и проверьте, исчезла ли ошибка. - Проверьте условиеЕсли один JS-файл загружается на нескольких страницах, а кнопка есть не везде, предусмотрите проверку наличия:
const btn = document.querySelector('#btn'); if (btn) { btn.addEventListener('click', handler); }. Сам обработчикhandlerдолжен быть определённой функцией, а не результатом её преждевременного вызова. Добавьте временныйconsole.warnдля отсутствующего элемента на нужной странице. Так скрипт не будет падать, но если кнопка должна существовать, найдите причину её отсутствия вместо молчаливого пропуска. - Для динамических элементов используйте делегированиеЕсли кнопка появляется после загрузки через JavaScript или фреймворк, регистрация при
DOMContentLoadedможет быть слишком ранней. В простой странице используйте делегирование на постоянном контейнере: назначьте обработчик родителю, а внутри черезevent.target.closest('.btn')проверьте нажатый элемент. В React/Vue обычно правильнее назначить обработчик в самом компоненте по правилам фреймворка, а не вручную искать его в DOM. Убедитесь, что обработчик не создаётся повторно на каждом обновлении, иначе события начнут срабатывать несколько раз. - Используйте опциональную цепочкуВыражение
btn?.addEventListener('click', handler)не выбросит ошибку приnull, но не объяснит, почему кнопка отсутствует. Используйте его только если элемент действительно необязателен. После исправления обновите страницу, откройте Console и нажмите кнопку: ожидается однократный вызов обработчика и отсутствие TypeError. Если ошибка осталась, сохраните сообщение, HTML-селектор, исходный код подключения и информацию о том, когда создаётся элемент; проверьте другие участки, где вызывается.addEventListener. - Пример кода по темеindex.htmlАтрибут defer откладывает выполнение скрипта до окончания разбора HTML, при этом порядок нескольких скриптов сохраняется.
<script src="app.js" defer></script>
#Примеры
document.addEventListener('DOMContentLoaded', () => {
const btn = document.querySelector('#btn');
if (!btn) {
console.warn('Элемент #btn не найден');
return;
}
btn.addEventListener('click', () => console.log('Клик'));
});
// Делегирование для элементов, которые появятся позже
document.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (item) console.log('Элемент', item.dataset.id);
});Для сборок на React/Vue не используйте getElementById внутри компонента до монтирования: применяйте ref или хуки (useEffect, onMounted). Если ошибка появляется только в продакшене, проверьте, не минифицируются ли id и не блокируют ли расширения браузера DOM-элементы. Справка по методам — .