Практический JavaScript: DOM, события и localStorage

Интенсивный курс для быстрого освоения манипуляций с DOM-деревом и клиентскими хранилищами [izecom.ru](https://izecom.ru/projects/programming-library/javascript-course/js-dom/). Вы научитесь создавать интерактивные компоненты и сохранять данные между сессиями [pokodem.ru](https://pokodem.ru/sozdanie-prostogo-todo-list-na-javascript-s-nulya-poshagovoe-rukovodstvo/).

1. Поиск элементов и изменение DOM-дерева

Приветствую на интенсивном курсе по Frontend-разработке! Вы уже знаете базовый синтаксис JavaScript, поэтому мы пропустим переменные и циклы, и сразу перейдем к тому, что делает веб-страницы живыми.

Как опытный ментор, я составил для вас пошаговый план на ближайший месяц. Наша цель — научиться связывать логику JavaScript с визуальной частью сайта и сохранять данные пользователя.

Интенсивный план обучения на 5 недель

  • Неделя 1. Поиск элементов и изменение DOM-дерева (Текущая тема). Изучаем querySelector, textContent, innerHTML и classList. Практика: создание переключателя темной темы.
  • Неделя 2. События и интерактивность. Разбираем addEventListener, объекты событий, всплытие и делегирование. Практика: создание интерактивных табов и аккордеонов.
  • Неделя 3. Динамическое управление элементами. Учимся создавать (createElement) и удалять (remove) узлы. Практика: разработка базового списка задач (To-Do List).
  • Неделя 4. Клиентские хранилища и формат JSON. Осваиваем localStorage, JSON.stringify и JSON.parse. Практика: сохранение пользовательских настроек и данных из форм после перезагрузки страницы.
  • Неделя 5. Итоговый проект. Объединение всех технологий. Практика: разработка простой админки для отслеживания процессов с сохранением состояния и фильтрацией данных.
  • Теперь перейдем к первой теме нашего интенсива.

    Что такое DOM и зачем он нужен

    Когда браузер загружает HTML-код страницы, он не просто рисует текст на экране. Он читает этот код и строит на его основе внутреннюю структуру данных, которая называется DOM (Document Object Model — объектная модель документа).

    > DOM — это представление HTML-документа в виде древовидной структуры объектов, с которой может взаимодействовать JavaScript. > > Студия веб-дизайна IZE

    Представьте себе чертеж дома. Чертеж — это ваш HTML-файл. На бумаге нарисованы двери, окна и стены, но вы не можете открыть нарисованную дверь. Когда строители возводят реальный дом по этому чертежу, появляется физический объект. DOM — это и есть тот самый «построенный дом». JavaScript выступает в роли умного дома: он может программно открыть дверь, перекрасить стену или пристроить новый балкон, не меняя исходный бумажный чертеж.

    !Схема DOM-дерева

    Каждый HTML-тег становится узлом (node) в этом дереве. Тег <body> — это родительский узел для всех видимых элементов, а вложенные в него <p> или <div> — дочерние узлы.

    Поиск элементов на странице

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

    Метод querySelector

    Метод document.querySelector() ищет на странице первый элемент, который соответствует указанному CSS-селектору, и возвращает его в виде объекта.

    Если на странице есть десять кнопок с классом .submit-btn, этот метод вернет только самую первую из них. Если элемент не найден, метод вернет null.

    Метод querySelectorAll

    Если вам нужно найти все элементы с определенным селектором (например, все элементы списка задач), используется document.querySelectorAll().

    Этот метод возвращает коллекцию элементов (NodeList). Хотя внешне она похожа на массив, это специальный объект браузера. Главное, что нужно о нем знать — его можно перебирать с помощью метода forEach.

    | Метод | Что возвращает | Когда использовать | Пример из жизни | | :--- | :--- | :--- | :--- | | querySelector | Один объект (первый найденный) | Нужна конкретная кнопка, заголовок или уникальный блок | Найти кнопку «Отправить форму» | | querySelectorAll | Коллекцию объектов (NodeList) | Нужно применить действие к группе однотипных элементов | Найти все карточки товаров на странице |

    Существуют и старые методы поиска, такие как document.getElementById() или document.getElementsByClassName(). Они работают немного быстрее, но querySelector стал индустриальным стандартом благодаря своей гибкости — вы можете передать в него любой сложный CSS-селектор, например .card > ul li:first-child.

    Изменение содержимого элементов

    Найдя элемент, мы можем изменить текст внутри него или даже вставить новый HTML-код.

    Свойство textContent

    Свойство textContent позволяет получить или изменить текстовое содержимое элемента. Оно игнорирует любые HTML-теги и работает исключительно с текстом.

    Если вы попытаетесь передать в textContent строку с HTML-тегами (например, <b>Привет</b>), браузер отобразит эти теги как обычный текст на экране. Это делает textContent абсолютно безопасным инструментом для вывода данных, которые ввел пользователь.

    Свойство innerHTML

    Свойство innerHTML работает иначе: оно позволяет читать и записывать полноценный HTML-код внутрь элемента. Браузер автоматически распарсит переданную строку и создаст новые узлы в DOM-дереве.

    Важное правило безопасности: никогда не используйте innerHTML для вставки данных, полученных от пользователя (например, из полей ввода или комментариев). Если злоумышленник напишет в комментарии код <script>код вируса</script>, свойство innerHTML выполнит его. Это называется XSS-атакой (межсайтовый скриптинг). Для пользовательских данных всегда используйте textContent.

    Управление стилями и классами

    Динамическое изменение внешнего вида — основа интерактива. В JavaScript есть два пути: менять стили напрямую или управлять CSS-классами.

    Прямое изменение стилей (inline-стили)

    У каждого DOM-элемента есть свойство style, через которое можно обращаться к CSS-свойствам. Важное отличие: в CSS свойства пишутся через дефис (background-color), а в JavaScript используется camelCase (backgroundColor).

    Этот подход добавляет стили прямо в атрибут style HTML-тега. Он имеет наивысший приоритет, но засоряет код. В современной разработке прямое изменение стилей используют редко — в основном для значений, которые вычисляются динамически (например, координаты курсора мыши).

    Управление через classList

    Лучшая практика — заранее описать все состояния элемента в CSS-файле (например, классы .active, .hidden, .dark-theme), а в JavaScript просто добавлять или удалять эти классы. Для этого используется объект classList.

    У classList есть несколько невероятно полезных методов:

  • add('class-name') — добавляет класс.
  • remove('class-name') — удаляет класс.
  • contains('class-name') — проверяет, есть ли класс у элемента (возвращает true или false).
  • toggle('class-name') — переключатель: если класса нет — добавляет его, если есть — удаляет.
  • Метод toggle — настоящий швейцарский нож для Frontend-разработчика. С его помощью реализация темной темы занимает пару строк кода.

    Представьте, что в вашем CSS есть такой код:

    Тогда в JavaScript переключение темы будет выглядеть так:

    При первом клике класс dark-theme добавится, и страница станет темной. При втором клике — удалится, и вернется светлая тема. Аналогичным образом создаются выпадающие меню, аккордеоны и модальные окна: вы просто переключаете класс .is-open или .hidden.

    Умение находить элементы через querySelector и манипулировать их классами через classList — это фундамент, на котором строится 80% визуального интерактива на современных сайтах.

    2. Управление классами и обработка событий

    Приветствую на второй неделе нашего интенсива по Frontend-разработке! На прошлой неделе мы научились находить элементы на странице и изменять их содержимое. Вы уже знаете, как работает объект classList и его методы add, remove и toggle. Но пока наши скрипты выполняются сразу при загрузке страницы.

    Чтобы сайт стал по-настоящему интерактивным, JavaScript должен уметь реагировать на действия пользователя: клики, ввод текста, прокрутку или наведение курсора. Сегодня мы свяжем управление классами с пользовательскими действиями через механизм событий.

    Событийная модель в браузере

    Браузер непрерывно следит за всем, что происходит на веб-странице. Когда пользователь совершает какое-либо действие, браузер генерирует событие (event).

    События — это сигналы, которые браузер посылает вашему коду. По аналогии с реальной жизнью: дверной звонок — это событие. Вы (как JavaScript-код) можете проигнорировать его, а можете отреагировать — подойти и открыть дверь.

    Самые частые события, с которыми вы будете работать:

  • click — клик левой кнопкой мыши по элементу.
  • submit — отправка HTML-формы.
  • keydown / keyup — нажатие или отпускание клавиши на клавиатуре.
  • input — изменение текста в поле ввода.
  • scroll — прокрутка страницы.
  • Прослушивание событий: метод addEventListener

    Чтобы заставить JavaScript реагировать на событие, нам нужно назначить обработчик события (event listener). Это функция, которая будет вызвана браузером ровно в тот момент, когда произойдет указанное действие.

    Современный и самый надежный способ сделать это — использовать метод addEventListener.

    Метод принимает два обязательных аргумента:

  • Название события в виде строки (например, 'click').
  • Функцию (колбэк), которая должна выполниться при наступлении события.
  • > Существуют и старые способы назначения событий, например, через свойства DOM-элементов: button.onclick = function() {...}. Однако этот подход считается устаревшим, так как он позволяет назначить только один обработчик на одно событие. Метод addEventListener позволяет «повесить» на одну кнопку хоть десять разных функций, и все они сработают по очереди.

    Объект события (Event Object)

    Когда происходит событие, браузер не просто вызывает вашу функцию. Он передает в нее специальный объект, который содержит все подробности о том, что именно произошло. По негласной конвенции этот параметр называют e или event.

    Самое важное свойство этого объекта — event.target. Оно содержит ссылку на конкретный DOM-элемент, с которым произошло событие. Это невероятно полезно, когда мы хотим узнать, на какую именно кнопку нажал пользователь, если кнопок много.

    Отмена стандартного поведения

    У многих HTML-элементов есть встроенное, стандартное поведение. Например, клик по ссылке <a> перенаправляет на другую страницу, а нажатие кнопки внутри <form> перезагружает страницу и отправляет данные на сервер.

    При разработке современных веб-приложений (Single Page Applications) мы часто хотим остановить это поведение и обработать данные самостоятельно с помощью JavaScript. Для этого используется метод preventDefault() объекта события.

    Представьте, что стандартное поведение — это поезд, отправляющийся по расписанию. Метод preventDefault() — это стоп-кран, который вы срываете, чтобы поезд никуда не уехал, пока вы не проверите билеты у всех пассажиров.

    Всплытие событий (Event Bubbling)

    Чтобы создавать сложные интерактивные компоненты, необходимо понимать концепцию всплытия событий.

    Представьте себе матрешку: маленькая фигурка находится внутри средней, а та — внутри большой. В HTML элементы тоже вложены друг в друга. Если вы кликнете по тексту внутри тега <button>, который лежит внутри <div>, который находится в <body>, вы фактически кликнете по всем этим элементам одновременно.

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

    !Схема всплытия событий в DOM-дереве

    Если у вас есть обработчики клика и на кнопке, и на контейнере <div>, при клике на кнопку сначала выполнится код кнопки, а затем — код контейнера.

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

    Всплытие событий открывает доступ к самому мощному паттерну в Frontend-разработке — делегированию событий (Event Delegation).

    Представьте, что вы разрабатываете список задач (To-Do List). У вас есть тег <ul>, внутри которого находится элементов <li>. В каждом <li> есть кнопка «Удалить».

    Неопытный разработчик найдет все кнопок через querySelectorAll и в цикле повесит на каждую из них свой addEventListener. Это плохой подход по двум причинам:

  • Это расходует оперативную память браузера.
  • Если вы динамически добавите -ю задачу через JavaScript, на ее кнопке «Удалить» не будет обработчика! Вам придется назначать его вручную при каждом добавлении.
  • Делегирование решает эту проблему элегантно: мы вешаем один обработчик события на общего родителя (тег <ul>). Благодаря всплытию, клик по любой кнопке внутри списка поднимется до <ul>, где мы его и поймаем. А чтобы понять, по какому именно элементу кликнули, мы используем event.target.

    !Интерактивная демонстрация всплытия и делегирования событий

    Пример реализации делегирования для списка задач:

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

    Практика: Создание интерактивных табов

    Давайте объединим управление классами и делегирование событий для создания классического компонента — вкладок (табов).

    Логика работы табов проста:

  • Есть группа кнопок-переключателей и группа блоков с контентом.
  • Только одна кнопка и один блок контента имеют класс .active.
  • При клике на кнопку мы удаляем класс .active у всех элементов и добавляем его только той кнопке, по которой кликнули, и соответствующему ей блоку контента.
  • В этом примере мы использовали пользовательские HTML-атрибуты data-*. Это стандартный способ хранить дополнительную информацию прямо в HTML-тегах, чтобы потом легко читать ее через JavaScript.

    Освоив связку classList + addEventListener + event.target, вы получаете полный контроль над визуальной логикой интерфейса. Вы можете создавать аккордеоны, модальные окна, выпадающие меню и слайдеры. На следующей неделе мы пойдем еще дальше и научимся не просто скрывать элементы, а физически создавать и удалять их из DOM-дерева.

    3. Хранение данных в localStorage через JSON

    Хранение данных в localStorage через JSON

    Приветствую на третьей неделе нашего интенсива по практическому JavaScript! На прошлых занятиях мы научились оживлять страницы: находить элементы в DOM-дереве, изменять их классы и реагировать на действия пользователя с помощью событий. Вы уже умеете создавать интерактивные табы и переключать темы оформления.

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

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

    Клиентское хранилище: память вашего браузера

    Исторически веб был «забывчивым». Сервер отправлял HTML-страницу, браузер ее отображал, и на этом их взаимодействие заканчивалось. Для сохранения данных (например, статуса авторизации) использовались Cookies — небольшие текстовые фрагменты, которые браузер постоянно пересылал на сервер при каждом запросе.

    С развитием сложных веб-приложений (Single Page Applications) появилась потребность хранить большие объемы данных прямо в браузере пользователя, не дергая сервер по пустякам. Так появился Web Storage API, который включает в себя два механизма: sessionStorage и localStorage.

    | Характеристика | localStorage | sessionStorage | Cookies | | :--- | :--- | :--- | :--- | | Срок жизни | Бессрочно (пока не удалит пользователь или код) | До закрытия вкладки браузера | Задается сервером (может быть бессрочным) | | Объем данных | Около 5 МБ | Около 5 МБ | Около 4 КБ | | Доступность | Любая вкладка с тем же доменом | Только текущая вкладка | Любая вкладка, отправляются на сервер | | Назначение | Настройки, кэш данных, корзина товаров | Временные данные формы, фильтры | Авторизация, отслеживание сессий |

    В рамках нашего курса мы сфокусируемся на localStorage, так как именно он позволяет создавать приложения, которые «помнят» пользователя даже спустя месяцы после последнего визита.

    Базовые операции с localStorage

    Объект localStorage встроен прямо в глобальный объект window вашего браузера. Он работает по принципу словаря: вы сохраняете значение под определенным именем (ключом), а затем по этому ключу можете его получить.

    Для управления данными используются четыре основных метода:

  • setItem(key, value) — сохраняет пару «ключ-значение».
  • getItem(key) — получает значение по ключу.
  • removeItem(key) — удаляет конкретную запись.
  • clear() — полностью очищает хранилище для текущего сайта.
  • Рассмотрим базовый пример работы с этими методами:

    > Вы можете визуально посмотреть, что хранится в вашем браузере прямо сейчас. Откройте инструменты разработчика (клавиша F12), перейдите на вкладку Application (Приложение) и в левом меню найдите раздел Local Storage. Это незаменимый инструмент при отладке скриптов.

    Проблема типов данных: всё становится строкой

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

    Если вы попытаетесь сохранить число, логическое значение или, что еще хуже, сложный объект, браузер автоматически преобразует их в строку.

    С примитивами (числами и булевыми значениями) проблема решается простым преобразованием типов после извлечения. Но настоящая катастрофа происходит при попытке сохранить объект или массив:

    Браузер применил стандартный метод приведения объекта к строке, и мы безвозвратно потеряли все данные. Строка "[object Object]" абсолютно бесполезна. Нам нужен способ превратить сложную структуру данных в строку так, чтобы потом ее можно было собрать обратно.

    Формат JSON: мост между объектами и строками

    Для решения проблемы сохранения сложных структур используется JSON (JavaScript Object Notation). Это легковесный текстовый формат обмена данными. Несмотря на название, сегодня он является абсолютным стандартом не только в JavaScript, но и в Python, PHP, Java и других языках.

    JSON выглядит почти как обычный объект JavaScript, но с более строгими правилами: все ключи и строковые значения должны быть обязательно заключены в двойные кавычки.

    В JavaScript встроены два мощных метода для работы с этим форматом:

  • JSON.stringify(data) — принимает объект или массив и превращает его в JSON-строку (сериализация).
  • JSON.parse(string) — принимает JSON-строку и конструирует из нее полноценный JavaScript-объект или массив (десериализация).
  • !Схема процесса сериализации и десериализации данных

    Давайте исправим наш предыдущий пример с профилем пользователя, используя магию JSON:

    Теперь мы можем сохранять в браузере списки задач, настройки интерфейса, содержимое корзины покупок — любые данные, которые можно описать массивами и объектами.

    Практика 1: Сохранение темы оформления

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

    Логика работы:

  • При загрузке страницы проверяем, есть ли запись о теме в localStorage.
  • Если есть и она равна "dark", добавляем класс dark-theme на тег <body>.
  • При клике на кнопку переключения темы мы меняем класс у <body> и сразу же записываем новое состояние в localStorage.
  • Этот простой скрипт кардинально улучшает пользовательский опыт. Человеку не придется заново включать темную тему при каждом переходе на новую страницу вашего сайта.

    Практика 2: Управление списком задач (To-Do List)

    Создание списка задач — классическая задача для Frontend-разработчика. Нам нужно хранить массив объектов, где каждый объект представляет одну задачу с ее текстом и статусом выполнения.

    Обратите внимание на паттерн: мы всегда держим актуальные данные в переменной tasks (в оперативной памяти), а при любом изменении этой переменной (добавление, удаление, изменение статуса) мы синхронизируем ее с localStorage.

    Безопасность и обработка ошибок

    При работе с localStorage и JSON.parse необходимо учитывать два важных нюанса.

    Во-первых, метод JSON.parse() очень строг. Если вы передадите ему некорректную строку (например, обычный текст вместо JSON), он выбросит критическую ошибку, которая остановит выполнение всего вашего JavaScript-кода на странице.

    Чтобы этого избежать, потенциально опасный код оборачивают в конструкцию try...catch:

    Во-вторых, никогда не храните в localStorage конфиденциальные данные: пароли, токены доступа к банковским API, личную переписку. Данные в localStorage никак не зашифрованы. Любой скрипт, запущенный на вашей странице (включая сторонние рекламные баннеры или виджеты аналитики), может прочитать содержимое хранилища. Это уязвимость известна как XSS (Cross-Site Scripting).

    Освоив связку DOM-событий, управления классами и сохранения данных в localStorage через JSON, вы получили полный набор инструментов для создания автономных клиентских приложений. На следующей неделе мы научимся брать данные из нашего массива tasks и динамически генерировать для них HTML-разметку, создавая элементы прямо из JavaScript.

    4. Создание компонентов и списков задач

    Создание компонентов и списков задач

    На прошлых этапах нашего интенсива мы научились находить элементы на странице, реагировать на клики пользователя и сохранять данные в память браузера. Однако до сих пор наши данные и интерфейс существовали отдельно друг от друга. Массив задач мог храниться в localStorage, но на экране пользователь видел лишь статичный HTML, написанный заранее.

    Настоящая магия Frontend-разработки начинается в тот момент, когда интерфейс начинает строиться динамически, опираясь на данные. Сегодня мы объединим все изученные технологии, чтобы создать полноценный интерактивный компонент — список задач (To-Do List), который умеет отображать данные, обновляться при действиях пользователя и сохранять свой прогресс.

    Источник истины: Состояние против DOM

    Главная ошибка начинающих разработчиков при создании интерфейсов — использование DOM-дерева в качестве хранилища данных.

    Представьте, что вы пишете код для удаления задачи. Неопытный разработчик найдет нужный тег <li> на странице, удалит его с помощью метода remove(), а затем попытается «прочитать» оставшиеся теги <li>, чтобы заново собрать массив задач и сохранить его в localStorage. В этом подходе DOM диктует, какие данные у нас есть.

    Профессиональный подход работает ровно наоборот. В основе любого надежного компонента лежит состояние (state) — обычная переменная в JavaScript (чаще всего объект или массив), которая хранит актуальные данные.

    DOM-дерево — это лишь визуальное отражение этого состояния. Если мы хотим удалить задачу, мы удаляем ее из массива в JavaScript, сохраняем обновленный массив в память, а затем даем команду интерфейсу перерисоваться на основе новых данных.

    !Схема взаимодействия состояния, DOM и localStorage

    Такой процесс обновления интерфейса на основе данных называется рендерингом. Этот же принцип лежит в основе современных фреймворков, таких как React или Vue.

    Динамическое создание элементов

    Чтобы интерфейс мог отражать наше состояние, нам нужны инструменты для создания HTML-тегов прямо из JavaScript. Для этого существует метод document.createElement(tagName).

    Он создает новый пустой HTML-элемент в оперативной памяти. Пока мы не добавим его на страницу, пользователь его не увидит.

    Рассмотрим пошаговый процесс создания элемента:

  • Создаем тег с помощью createElement.
  • Наполняем его контентом через textContent.
  • Добавляем нужные CSS-классы через classList.add().
  • Вставляем готовый элемент внутрь существующего контейнера на странице с помощью метода appendChild().
  • Этот метод гораздо безопаснее, чем использование innerHTML. При вставке через innerHTML браузер заново парсит всю строку как HTML-код, что открывает уязвимость для XSS-атак (если текст задачи ввел злоумышленник). Метод textContent вставляет данные исключительно как безопасный текст.

    Проектируем To-Do List: Шаг за шагом

    Давайте создадим полноценный список задач. Нам понадобится минимальная HTML-разметка, которая будет служить «скелетом» нашего приложения:

    Вся магия будет происходить внутри пустого тега <ul id="todo-list"></ul>.

    Шаг 1: Инициализация состояния

    Первым делом мы должны объявить наше состояние и попытаться загрузить предыдущие задачи из localStorage. Если хранилище пустое, мы начнем с пустого массива.

    Каждая задача в массиве будет не просто строкой, а объектом с тремя свойствами: уникальным идентификатором (id), текстом задачи (text) и статусом выполнения (completed).

    Шаг 2: Функция рендеринга

    Теперь напишем функцию renderTasks(). Ее единственная задача — взять текущий массив tasks и превратить его в HTML-элементы на странице.

    Важное правило: перед тем как отрисовать список, функция должна полностью очистить контейнер, чтобы задачи не дублировались.

    Шаг 3: Добавление новой задачи

    Когда пользователь нажимает кнопку «Добавить», мы не трогаем DOM напрямую. Мы обновляем массив tasks, сохраняем его и вызываем функцию рендеринга.

    Шаг 4: Удаление с помощью делегирования событий

    Вместо того чтобы вешать обработчик событий на каждую кнопку «Удалить» (которых может быть сотня), мы используем паттерн делегирования событий, изученный на второй неделе. Мы повесим один слушатель на весь контейнер <ul>.

    В конце нашего скрипта нам остается только один раз вызвать renderTasks(), чтобы при первой загрузке страницы отрисовались задачи, извлеченные из памяти.

    Архитектурное преимущество

    Обратите внимание на элегантность этого подхода. Функции добавления и удаления ничего не знают о том, как именно элементы выглядят на странице. Их зона ответственности — изменить массив tasks и обновить localStorage.

    Вся логика создания HTML сосредоточена в одном месте — в функции renderTasks(). Если завтра вы решите добавить к задаче иконку или изменить структуру тегов, вам придется поменять код только в одной функции. Это делает код масштабируемым и устойчивым к ошибкам.

    Освоив этот паттерн (Состояние → Рендеринг → Событие → Изменение состояния), вы сделали огромный шаг от верстальщика, который просто «оживляет» кнопки, к Frontend-инженеру, который проектирует надежные веб-приложения. На следующей, финальной неделе нашего интенсива, мы применим эти знания для создания простой панели администратора, которая будет агрегировать данные и управлять сложными компонентами.

    5. Обмен данными для панели администратора

    Обмен данными для панели администратора

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

    Сегодня мы совершим переход от создания отдельных компонентов к проектированию архитектуры приложения. Мы разберем, как связать несколько страниц одного сайта воедино, используя localStorage в качестве единого источника истины, и создадим простую панель администратора (Admin Dashboard) для отслеживания пользовательской активности.

    Границы памяти: Концепция Origin

    Главный секрет обмена данными между страницами кроется в том, как браузер изолирует информацию. Память localStorage не привязана к конкретному HTML-файлу. Она привязана к источнику (Origin).

    Источник состоит из трех компонентов: протокола (например, https://), доменного имени (например, gurufy.com) и порта. Если две страницы имеют одинаковый источник, они имеют общий, абсолютно идентичный доступ к одному и тому же объекту localStorage.

    > Данные, сохраненные на странице index.html, мгновенно доступны для чтения на странице admin.html, если обе страницы открыты на одном и том же сайте.

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

    !Схема обмена данными между страницами через localStorage

    Разделение ответственности

    При проектировании системы из нескольких страниц важно четко разделить их роли. Смешивание логики сбора данных и их анализа приводит к запутанному коду.

    | Характеристика | Пользовательская страница (To-Do List) | Панель администратора (Admin Dashboard) | | :--- | :--- | :--- | | Основная задача | Ввод данных, взаимодействие с интерфейсом | Чтение данных, агрегация, визуализация | | Операции с хранилищем | Чтение при загрузке, частая запись (setItem) | В основном чтение (getItem), редко удаление | | Формат отображения | Интерактивные списки, формы, кнопки | Таблицы, сводные числа, графики |

    Шаг 1: Подготовка данных на стороне пользователя

    Чтобы панели администратора было с чем работать, пользовательская страница должна сохранять не только сами задачи, но и метаданные — информацию о действиях.

    Давайте модифицируем логику нашего To-Do List из прошлой статьи. Теперь, помимо массива задач, мы будем вести объект статистики.

    Теперь каждый раз, когда пользователь нажимает кнопку «Добавить» или отмечает задачу как выполненную, мы вызываем updateStats('add') или updateStats('complete'). Данные надежно оседают в памяти браузера.

    Шаг 2: Агрегация и вычисления в панели администратора

    Перейдем к файлу admin.html и его скрипту. Задача администратора — не просто показать сырые данные, а извлечь из них пользу. Это называется агрегацией данных.

    Например, мы хотим знать процент выполнения задач. Для этого используем простую формулу:

    Где — процент выполнения, — количество выполненных задач (Completed), а — общее количество добавленных задач (Total).

    Напишем функцию рендеринга для админки:

    Этот код берет сухие цифры из localStorage, проводит математические вычисления и динамически строит DOM-дерево с понятным отчетом.

    Шаг 3: Магия реального времени и событие storage

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

    Чтобы сделать панель по-настоящему интерактивной, JavaScript предоставляет специальное событие — storage event.

    Это событие уникально. Оно срабатывает на объекте window, но только в других вкладках или окнах того же источника. Оно никогда не срабатывает на той вкладке, которая произвела изменение. Это идеальный механизм для синхронизации интерфейсов.

    Добавим слушатель событий в код нашей панели администратора:

    Теперь, если вы откроете index.html в левой половине экрана, а admin.html в правой, вы увидите настоящую магию. При добавлении задачи в левом окне, статистика в правом окне обновится мгновенно, без перезагрузки страницы. Вы только что создали реактивное приложение с синхронизацией состояния!

    Итоговый проект интенсива

    Вы прошли путь от базового понимания DOM-дерева до проектирования многостраничной архитектуры с синхронизацией данных. Теперь пришло время объединить все навыки в финальном мини-проекте.

    Ваша задача — разработать приложение «Менеджер продуктивности», состоящее из двух страниц:

  • Рабочая область (index.html)
  • - Интерактивный To-Do List (добавление, удаление, отметка о выполнении). - Переключатель темной/светлой темы (через classList.toggle). - Сохранение задач и выбранной темы в localStorage. - Использование делегирования событий для управления списком.

  • Панель управления (admin.html)
  • - Чтение данных из localStorage. - Отображение общего количества задач и процента выполненных. - Вывод списка последних 5 добавленных задач. - Автоматическое обновление данных в реальном времени при изменениях на рабочей странице (использование события storage). - Применение той же темы оформления (темной или светлой), которую пользователь выбрал на главной странице.

    Этот проект станет отличным дополнением к вашему портфолио. Он демонстрирует понимание состояния, умение безопасно работать с DOM, знание клиентских хранилищ и способность проектировать взаимодействие между независимыми частями системы.