<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 15:12:38 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="y-M5B6m_jakSSZpsg7pW4ILvF3Ai5dVXS7xWqe5ionEkMvIwW8EgyaQKvvSPtaaXQuY62irSK_X2XMz9vGVFHw";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>HTML-дерево | JS: Деревья</title>
<meta name="description" content="HTML-дерево / JS: Деревья: Учимся работать с другими деревьями на примере разметки HTML">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-trees/lessons/html/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="HTML-дерево">
<meta property="og:title" content="JS: Деревья">
<meta property="og:description" content="HTML-дерево / JS: Деревья: Учимся работать с другими деревьями на примере разметки HTML">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-trees/lessons/html/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="M_etAxGwsCa2e4XiWpnBEAp2hEh2a3hailWWddpyzhncJmY0484dRgA4oXpWljFnyn-p4n5chvg3tQwhiHUpdw" />
<script src="/vite/assets/inertia-INZxX8jp.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-6pOtQ3OW.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNywicHVyIjoiYmxvYl9pZCJ9fQ==--2d5cbbf5c3b4a73ae4b2c50632305d78f5872e4d/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--2e84f5f94140ee4e22019ac479c290ef48c3fac8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--e2c6c0775e2308e42fbc5dc592ba2db0470632ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T15:12:38.479Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"2broos-iKUrCUn4U9s6ILfTQRxYg_ZHtHjdAeYO-K4U2ayOVPdyEKnQRWoz6wXhaNNlqvCjKb0-j19ot0bnM6w","topics":[{"id":67685,"title":"Для всех ребят, кому заходит также туго, как и мне. Решил описать по шагам выполнение функции из ТЕОРИИ. И для себя тоже, чтобы окончательно понять и получить какой-то фидбэк от наставников и более продвинутых студентов. Также хочу отметить, что на понимание сути работы функции у меня ушли примерно сутки. Это пока что было самое сложное для меня на хекслете. Я не знаю, нормально ли, что я потратил столько времени на это. Кто-нибудь есть, кто это все понял сразу или почти сразу?\nТеперь к функции.\nВо-первых, я сразу не обратил внимание где выход из функции filterEmpty и зациклился на мысли, что map должен отработать, потом только filter. Но нет. В этой функции filter() более высокого уровня рекурсии начнет работать после того, когда map более глубокого уровня отработал. \nЧитать шаги будет утомительно, но может кому-то поможет.\nПо шагам это работает примерно так:\n1. Функция вызывается с параметром htmlTree.\n2. Получаем массив из его детей (в нашем случае это содержимое htmlTree.children).\n3. Заходим с ним в map, который начинает формирование того самого окончательного массива filtered, который по итогу работы функции будет содержать окончательный набор детей для html (он же самый первый tree). Т.к. htmlTree.children содержит только один элемент с type: 'tag-internal' тут выполняется if и происходит провал в рекурсию на один уровень, НО в этом месте функция, вызванная на на шаге один ожидает результат! Нужно это запомнить. А чтобы понять каким в этом месте будет возврат, нужно дойти до самого глубокого элемента в самой последней ветке дерева.\n4. На 1 уровне рекурсии формируем массив детей для body (это 5 вложенных элементов). \n5. Мапаем этот массив детей. \n6. Первый ребенок у body это тег h1 с type: 'tag-internal' - соответственно снова рекурсивно вызывается функция. И провал на 2 уровень рекурсии.\n7. Все по новой находим детей - h1.children.\n8. Мапаем этот массив h1.children. \n9. В нем всего один ребёнок. Визуально получается ситуация следующая: [{type: 'text', content: 'Сообщество',}] .map(.........). Этот мап уже может вернуть конкретный результат сразу, без рекурсии, и завершить формирование массива filtred, что он успешно и делает так как if не выполняется, а выполняется return node (node при этом равно {type: 'text', content: 'Сообщество',}).\n10. Тут происходит важный момент - мап из 9 пункта полностью завершается (он ОТМАПАЛ свой массив h1.children состоящий всего из одного элемента). Соответственно выполнение функции продолжается дальше, вне мапа. МЫ ПОПАДАЕМ в filter().\n11. filter() выполняется для этого массива [{type: 'text', content: 'Сообщество',}]. Заходим внутри filter() в swith, выполняется кейс text, возвращается true. Единственный элемент массива по результатам работы filter() остается на своем месте.\n12. Дальше происходит !НАКОНЕЦ выход из функции вот этот - return { ...tree, children: filtered }. он возвращает результат следующего содержания - {\n name: 'h1',\n type: 'tag-internal',\n children: [\n {\n type: 'text',\n content: 'Сообщество',\n }\nи возвращает этот результат в место предыдущего вызова функции внутри map, которое было на шаге 6 (2 уровень рекурсии).\n13. Наш возврат записывается в формируемый мапом из 1 уровня рекурсии массив (для body).\n14. В этот мап заходит второй элемент из body.children.\n15. Повторяются шаги с 7 по 12 для элемента p. Итого на первом уровне рекурсии (вызов в переменной tree находится элемент {name: body, ....}) в результирующем массиве работы для map уже 2 элемента.\n16. Затем в map попадают 3 и 4 элементы body.children. Они оба имеют type: 'tag-leaf' и записываются в результат как есть благодаря команде return node.\n17. В результирующем массиве работы для map из первого уровня рекурсии уже 4 элемента.\n18. На сцене (внутри body.children.map()) ПЯТЫЙ элемент. Это div c двумя детьми. Происходит рекурсивный вызов и провал на 2 уровень.\n19. Находятся дети для div. Это два пустых дива с type: 'tag-internal'.\n20. Происходит Мап массива внутри которого дети div с классом hexlet-community (напоминаю, это два пустых дива).\n21. Мап первого ребенка приводит к рекурсивному вызову и провалу на 3 уровень.\n22. У текущего элемента див нет детей и div.children === []; \n23. Вот тут немного скользкий момент. Как я понимаю дальше функция map принимает пустой массив и возвращает тоже пустой массив.\n24. Тоже самое происходит и с filter(). Так как сейчас filtred = [] , то и filter() вернет [].\n25. Происходит возврат на 2 уровень с возвратом пустого div в неизменном виде. (Этот возвращенный элемент записывается в результирующий массив filtred 2 уровня).\n26. Второй пустой div повторяет путь своего предшественника.\n27. На этом этапе завершается отработка мапа для дива с классом hexlet-community и filtred в его контексте вызова содержит в себе два пустых дива.\n28. filtred с двумя пустыми дивами попадает в filter(). На этом этапе наши пустые дивы отфильтровываются и filtred для дива с классом hexlet-community становится равен [];\n29. return и выход на 1 уровень рекурсии который до сих пор ждет на шаге 4 чтобы завершить мап.\n30. Мап завершается и filtred в контексте вызова для body попадает в filter(). (сейчас в нем 5 элементов)\n31. Элементы с 1 по 4 проходят свои проверки и записываются в filtred, а ПЯТЫЙ элемент имеет children = []. Он отфильтровывается и filtred для body содержит 4 элемента.\n32. Происходит return и body возвращается с обновленным children из 4 элементов на 0 уровень внутри мапа самого первого вызова функции.\n35. Мап для html может завершиться.\n36. Начинает работать filter() для html.children (внутри которого только один элемент body, дети которого уже отфильтрованы и дети которых тоже уже отфильтрованы - рекурсия как никак). filter() возвращает массив из одного элемента body.\n37. Возврат значения самого первого вызова для htmlTree с обновленным массивом children.\n","plain_title":"Для всех ребят, кому заходит также туго, как и мне. Решил описать по шагам выполнение функции из ТЕОРИИ. И для себя тоже, чтобы окончательно понять и получить какой-то фидбэк от наставников и более продвинутых студентов. Также хочу отметить, что на понимание сути работы функции у меня ушли примерно сутки. Это пока что было самое сложное для меня на хекслете. Я не знаю, нормально ли, что я потратил столько времени на это. Кто-нибудь есть, кто это все понял сразу или почти сразу? Теперь к функции. Во-первых, я сразу не обратил внимание где выход из функции filterEmpty и зациклился на мысли, что map должен отработать, потом только filter. Но нет. В этой функции filter() более высокого уровня рекурсии начнет работать после того, когда map более глубокого уровня отработал. Читать шаги будет утомительно, но может кому-то поможет. По шагам это работает примерно так: 1. Функция вызывается с параметром htmlTree. 2. Получаем массив из его детей (в нашем случае это содержимое htmlTree.children). 3. Заходим с ним в map, который начинает формирование того самого окончательного массива filtered, который по итогу работы функции будет содержать окончательный набор детей для html (он же самый первый tree). Т.к. htmlTree.children содержит только один элемент с type: 'tag-internal' тут выполняется if и происходит провал в рекурсию на один уровень, НО в этом месте функция, вызванная на на шаге один ожидает результат! Нужно это запомнить. А чтобы понять каким в этом месте будет возврат, нужно дойти до самого глубокого элемента в самой последней ветке дерева. 4. На 1 уровне рекурсии формируем массив детей для body (это 5 вложенных элементов). 5. Мапаем этот массив детей. 6. Первый ребенок у body это тег h1 с type: 'tag-internal' - соответственно снова рекурсивно вызывается функция. И провал на 2 уровень рекурсии. 7. Все по новой находим детей - h1.children. 8. Мапаем этот массив h1.children. 9. В нем всего один ребёнок. Визуально получается ситуация следующая: [{type: 'text', content: 'Сообщество',}] .map(.........). Этот мап уже может вернуть конкретный результат сразу, без рекурсии, и завершить формирование массива filtred, что он успешно и делает так как if не выполняется, а выполняется return node (node при этом равно {type: 'text', content: 'Сообщество',}). 10. Тут происходит важный момент - мап из 9 пункта полностью завершается (он ОТМАПАЛ свой массив h1.children состоящий всего из одного элемента). Соответственно выполнение функции продолжается дальше, вне мапа. МЫ ПОПАДАЕМ в filter(). 11. filter() выполняется для этого массива [{type: 'text', content: 'Сообщество',}]. Заходим внутри filter() в swith, выполняется кейс text, возвращается true. Единственный элемент массива по результатам работы filter() остается на своем месте. 12. Дальше происходит !НАКОНЕЦ выход из функции вот этот - return { ...tree, children: filtered }. он возвращает результат следующего содержания - { name: 'h1', type: 'tag-internal', children: [ { type: 'text', content: 'Сообщество', } и возвращает этот результат в место предыдущего вызова функции внутри map, которое было на шаге 6 (2 уровень рекурсии). 13. Наш возврат записывается в формируемый мапом из 1 уровня рекурсии массив (для body). 14. В этот мап заходит второй элемент из body.children. 15. Повторяются шаги с 7 по 12 для элемента p. Итого на первом уровне рекурсии (вызов в переменной tree находится элемент {name: body, ....}) в результирующем массиве работы для map уже 2 элемента. 16. Затем в map попадают 3 и 4 элементы body.children. Они оба имеют type: 'tag-leaf' и записываются в результат как есть благодаря команде return node. 17. В результирующем массиве работы для map из первого уровня рекурсии уже 4 элемента. 18. На сцене (внутри body.children.map()) ПЯТЫЙ элемент. Это div c двумя детьми. Происходит рекурсивный вызов и провал на 2 уровень. 19. Находятся дети для div. Это два пустых дива с type: 'tag-internal'. 20. Происходит Мап массива внутри которого дети div с классом hexlet-community (напоминаю, это два пустых дива). 21. Мап первого ребенка приводит к рекурсивному вызову и провалу на 3 уровень. 22. У текущего элемента див нет детей и div.children === []; 23. Вот тут немного скользкий момент. Как я понимаю дальше функция map принимает пустой массив и возвращает тоже пустой массив. 24. Тоже самое происходит и с filter(). Так как сейчас filtred = [] , то и filter() вернет []. 25. Происходит возврат на 2 уровень с возвратом пустого div в неизменном виде. (Этот возвращенный элемент записывается в результирующий массив filtred 2 уровня). 26. Второй пустой div повторяет путь своего предшественника. 27. На этом этапе завершается отработка мапа для дива с классом hexlet-community и filtred в его контексте вызова содержит в себе два пустых дива. 28. filtred с двумя пустыми дивами попадает в filter(). На этом этапе наши пустые дивы отфильтровываются и filtred для дива с классом hexlet-community становится равен []; 29. return и выход на 1 уровень рекурсии который до сих пор ждет на шаге 4 чтобы завершить мап. 30. Мап завершается и filtred в контексте вызова для body попадает в filter(). (сейчас в нем 5 элементов) 31. Элементы с 1 по 4 проходят свои проверки и записываются в filtred, а ПЯТЫЙ элемент имеет children = []. Он отфильтровывается и filtred для body содержит 4 элемента. 32. Происходит return и body возвращается с обновленным children из 4 элементов на 0 уровень внутри мапа самого первого вызова функции. 35. Мап для html может завершиться. 36. Начинает работать filter() для html.children (внутри которого только один элемент body, дети которого уже отфильтрованы и дети которых тоже уже отфильтрованы - рекурсия как никак). filter() возвращает массив из одного элемента body. 37. Возврат значения самого первого вызова для htmlTree с обновленным массивом children. ","creator":{"public_name":"Артём Мельников","id":346795,"is_tutor":false},"comments":[{"creator":{"public_name":"Pavel Kolotiy","id":411391,"is_tutor":false},"id":162893,"body":"Спасибо, нужно этот разбор внести в теорию, это супер полезно!!!\n","topic_id":67685},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":142001,"body":"Суперклассный разбор! Спасибо большое! Надеюсь остальным будет полезно. Для понимания рекурсивных вызовов, их действительно лучше всего расписывать в дерево вызовов - тогда становится понятней что как вызывается.","topic_id":67685},{"creator":{"public_name":"Артем Тажитдинов","id":464127,"is_tutor":false},"id":146073,"body":"Две пиалы чая этому господину. Спасибо)","topic_id":67685},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":179962,"body":"**Антон Уразгильдин**, тикет завел на расширение урока. Спасибо","topic_id":67685},{"creator":{"public_name":"Екатерина Сайгы","id":279985,"is_tutor":false},"id":181157,"body":"Спасибо, Вам, большое! как я рада, что зашла обсуждения почитать ))","topic_id":67685},{"creator":{"public_name":"Антон Уразгильдин","id":589784,"is_tutor":false},"id":179830,"body":"Браво! Всю голову бы ломал без этого прекрасного разбора! Спасибо!\nНу а методисты явно недоработали в этом уроке, существующего объяснения работы функции в уроке явно недостаточно","topic_id":67685}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":81497,"title":"Добрый день!\nСоздалось впечатление, что теории для решения последних двух задач как-то недостаточно. Поделитесь, где еще вы черпали информацию о деревьях для решения этих задачек?) Заранее спасибо.","plain_title":"Добрый день! Создалось впечатление, что теории для решения последних двух задач как-то недостаточно. Поделитесь, где еще вы черпали информацию о деревьях для решения этих задачек?) Заранее спасибо. ","creator":{"public_name":"Svetlana177","id":269976,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":166627,"body":"**Pro_svet**, здравствуйте! Тут сложно что-то новое придумать, никаких тайных знаний нет, все что сами знаем, выложили в уроки) На самом деле нужна практика. Работа с деревьями — это одна из сложнейших задач и тянет за собой рекурсию, тоже непростая тема. Возможно вам еще поможет книга \"Грокаем алгоритмы\", в ней нет ничего нового, но возможно вам зайдет таже информация, поданная другими словами.","topic_id":81497}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":72037,"title":"Здравствуйте, подскажите, пожалуйста, почему не проходит тест? Ошибка TypeError: Cannot read properties of undefined (reading 'map')\nВ браузере локально и на replit все отрабатывает корректно.. совсем не пойму что ни так(\nhttps://ru.hexlet.io/code_reviews/666467","plain_title":"Здравствуйте, подскажите, пожалуйста, почему не проходит тест? Ошибка TypeError: Cannot read properties of undefined (reading 'map') В браузере локально и на replit все отрабатывает корректно.. совсем не пойму что ни так( https://ru.hexlet.io/code_reviews/666467 ","creator":{"public_name":"Hatewait","id":137354,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksandr Litvinov","id":117182,"is_tutor":true},"id":150598,"body":"Ошибка говорит о том, что `map` не может итерироваться по `newTree.children`, так как там пусто (`undefined`). Вы обрабатывали вариант, когда в функцию приходит, например, пустое дерево?\n","topic_id":72037}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":69689,"title":"Здравствуйте) Направьте пожалуйста - я уже кучу раз переписала ф-ию, и кажется мозг вообще не хочет соображать, хотя все уроки по деревьям до этого проскочила легко. Похоже там снова нужно заново переписывать, т.к. в дебри понесло.\n\nhttps://ru.hexlet.io/code_reviews/628159?submission_id=801739","plain_title":"Здравствуйте) Направьте пожалуйста - я уже кучу раз переписала ф-ию, и кажется мозг вообще не хочет соображать, хотя все уроки по деревьям жо этого проскочила легко. Похоже там снова нужно заново переписывать, т.к. в дебри понесло. https://ru.hexlet.io/code_reviews/628159 ","creator":{"public_name":"Евгения Ващенко","id":405330,"is_tutor":false},"comments":[{"creator":{"public_name":"Евгений Самута","id":398223,"is_tutor":false},"id":145939,"body":"1. Класс может быть и у дерева. Пока что ты проверяешь только детей и возвращаешь детей.\n2. У тебя сейчас так, что если тип у ребенка не tag-internal, то идет возврат ребенка в неизмененном виде.\n```\n const newChildren = children.map((child) => {\n if (child.type !== 'tag-internal') {\n return child;\n }\n```\nНо у детей с другими типами тоже может быть искомый класс.\n\n3. Возврат должен быть нового дерева.","topic_id":69689},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":145965,"body":"Здравствуйте!\n\nЕвгений, спасибо за помощь! Евгения, вам помогли советы? Пишите, если понадобится ещё помощь!","topic_id":69689},{"creator":{"public_name":"Евгения Ващенко","id":405330,"is_tutor":false},"id":146116,"body":"**Ivan Gagarinov**, уже разобралась) Но намучалась с этим заданием))","topic_id":69689},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":146111,"body":"**Евгения Ващенко**, давайте разбираться по порядку. Попробуйте описать алгоритм простыми словами алгоритм, без кода. ","topic_id":69689},{"creator":{"public_name":"Евгения Ващенко","id":405330,"is_tutor":false},"id":146026,"body":"**Ivan Gagarinov**, здравствуйте. Сначала помогли, а потом снова не помогли( Я что-то чем дальше, тем больше закапываюсь в этом задании, ни над одним еще столько не сидела\n\nhttps://ru.hexlet.io/code_reviews/628159?submission_id=803267","topic_id":69689}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":69873,"title":"Подскажите куда думать... Из принципа хочется самой решить упражнение.\nhttps://ru.hexlet.io/code_reviews/631432","plain_title":"Подскажите куда думать... Из принципа хочется самой решить упражнение. https://ru.hexlet.io/courses/js-trees/lessons/html/exercise_unit ","creator":{"public_name":"Анна Жарова","id":415137,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":146238,"body":"Несколько замечаний по коду:\n\n```javascript\nreturn { newTree, children: changes };\n```\n\nв этом месте создаётся объект с ключами `newTree` и `children`. Скорее всего вы хотели дополнить объект (для этого нужно использовать деструктуризацию `{ ...newTree, children }`\n\nВ решении идёт вызов `changeClass(node);`, но эта функция не принимает один параметр, она принимает три параметра.\n\nСейчас у вас идёт обработка детей, но нет обработки текущего корневого узла, а ведь он тоже имеет имя класса, которое нужно проверить. Попробуйте написать решение сначала без рекурси. Напишите функцию, которая бы обрабатывала один узел. После этого вам останется добавить обработку детей с рекурсивным вызовом этой же функции.","topic_id":69873}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":81670,"title":"https://ru.hexlet.io/code_reviews/820831\n Можете подсказать что здесь происходит, по моим прикидкам филтр перезаписывает класс а функция мап просто все возвращает. Это так должно работать ?","plain_title":"https://ru.hexlet.io/code_reviews/820831 Можете подсказать что здесь происходит, по моим прикидкам филтр перезаписывает класс а функция мап просто все возвращает. Это так должно работать ? ","creator":{"public_name":"Раджаб Амирханов","id":445202,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":166784,"body":"**Раджаб**, в решении учителя рекурсия вызывается только на узлах. То есть берутся дочерние элементы, и на них рекурсивно вызывается эта же функция, которая была вызвана на родителе.\n\nПо своему решению: обратитесь пожалуйста к своему наставнику, попросите его сделать ревью решения. Вы можете задать ему любые вопросы по решению и попросить подробно все объяснить.","topic_id":81670},{"creator":{"public_name":"Раджаб Амирханов","id":445202,"is_tutor":false},"id":166745,"body":"Я правильно понимаю что в эталонном решении дважды вызывается рекурсия ? Если да, то как это работает ?","topic_id":81670}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":66952,"title":"Вопрос по теории: прошу помочь разобраться с фильтрацией потомков в map.\n1) Непонятен этот момент `.map((node) => {\n // Перед фильтрацией отфильтровываем всех потомков\n if (node.type === 'tag-internal') {\n return filterEmpty(node);\n }\n return node;\n })`\n\nИ ниже комментарий в коде:\n> К этому моменту мы уже отфильтровали пустых потомков\n\nЧто значит отфильтровываем пустых потомков?\nИ чем они будут заменены?\nЧто-то я в упор не понимаю(\n\n2) Что значит запись `return { ...tree, children: filtered };`? Через двоеточие перезаписывается значение children? Что-то я такого не помню ранее\n\n3) Кажется кое-что начинает доходить: мы в мапе каждый раз вызываем filterEmpty(node), поэтому каждый раз срабатывает и часть кода `.filter((node)` с тремя условиями фильтрации, верно?","plain_title":"Прошу помочь разобраться с фильтрацией потомком в map. 1) Непонятен этот момент .map((node) => { // Перед фильтрацией отфильтровываем всех потомков if (node.type === 'tag-internal') { return filterEmpty(node); } return node; }) Что значит отфильтровываем пустых потомков?) Такого еще не было, я раз 10 прочел этот урок, прочитал ниже ответы Ивана, я просто в упор не понимаю. ","creator":{"public_name":"Igor","id":256310,"is_tutor":false},"comments":[{"creator":{"public_name":"Igor","id":256310,"is_tutor":false},"id":140603,"body":"Максим, спасибо за ответ! \n","topic_id":66952},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":140583,"body":"Вы верно предположили. Посмотрите на этот код:\n\n```\n .map((node) => {\n // Перед фильтрацией отфильтровываем всех потомков\n if (node.type === 'tag-internal') {\n return filterEmpty(node);\n }\n return node;\n })\n```\nЕсли тег не имеет тип 'tag-internal', то он возвращается как есть. А если он имеет тип 'tag-internal', то для него рекурсивно вызывается функция filterEmpty()\n\nВспомните, как работает фильтрация - все элементы, которые не удовлетворяют условию, не попадают в итоговый массив:\n\n```\n case 'tag-internal': {\n const { children } = node;\n return children.length > 0;\n }\n```\n\nТаким образом, все пустые теги 'tag-internal' (у которого нет детей) отфильтровываются\n\n> Через двоеточие перезаписывается значение children?\n\nВерно, тут создаётся новый объект при помощи спред оператора, а дети перезаписываются, меняются на отфильтрованных детей. Дети перезаписываются, так как то, что стоит справа, имеет приоритет при слиянии:\n\n```\nconst obj1 = {key1: \"value1\", key2: \"value2\"};\n\nconst newObj = {...obj1, key2: \"hello\"};\n\nconsole.log(newObj); // => { key1: 'value1', key2: 'hello' }\n```\n\nПробегитесь еще раз по материалу [этого](https://ru.hexlet.io/courses/js-objects/lessons/spread-operator/theory_unit) урока\n\n","topic_id":66952}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":68443,"title":"почему-то в одном месте меняется имя класса, хотя по не должно https://ru.hexlet.io/code_reviews/607819.\nпрошу помощи","plain_title":"почему-то в одном месте меняется имя класса, хотя по не должно https://ru.hexlet.io/code_reviews/607819. прошу помощи ","creator":{"public_name":"Алексей Письмеров","id":421851,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":143415,"body":"Тесты показывают, что имя класса не поменялось. То есть в исходном дереве hexlet-community не поменялось на new-class. В вашем решении неправильно присваивается новый класс: `node.className === newClass` - должен быть один знак равно.","topic_id":68443},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":143825,"body":"Вы ошибаетесь. Зелёным и знаком \"минус\" тесты отмечают то, что ожидалось (expected) и не пришло. Красным и знаком \"плюс\" тесты ометчают что пришло (received), но не должно было. Тесты сейчас показывают:\n\n```\n - \"className\": \"new-class\",\n + \"className\": \"hexlet-community\",\n```\n\nто есть ожидалось `new-class`, а пришло `hexlet-community`\n\n> с одним знаком = вывод не изменился\n\nЭто не единственная ошибка в вашем решении. У вас никак не используется результат мапа, внутри которого рекурсивно вызывается change. Map всегда возвращает отображение массива - новый массив с таким же количеством элементов. Вам нужно добавить сохранение изменённых элементов.","topic_id":68443},{"creator":{"public_name":"Алексей Письмеров","id":421851,"is_tutor":false},"id":143650,"body":"нет. тесты ожидают \"className\": \"hexlet-community\", а получают \"className\": \"new-class\". хотя не должно ничего меняться, ведь для замены стоит проверка: \nif (node.className === oldClass) {\n node.className = newClass\n }\nи я не понимаю почему код сюда заходит. Решение я уже сбрасывал. с одним знаком = вывод не изменился.","topic_id":68443}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":70148,"title":"Здравствуйте. Почему у меня пишет ошибку undefined? Я с помощью .map сначала проверяю, содержат ли дети свойство oldclass, если содержат, запускаю рекурсию, затем проверяю наличие старого класса, если старый класс есть, меняю на новый класс. Что не так? Подскажите \nМое ревью: https://ru.hexlet.io/code_reviews/634561","plain_title":"Здравствуйте. Почему у меня пишет ошибку undefined? Я с помощью .map сначала проверяю, содержат ли дети свойство oldclass, если содержат, запускаю рекурсию, затем проверяю наличие старого класса, если старый класс есть, меняю на новый класс. Что не так? Подскажите Мое ревью: https://ru.hexlet.io/code_reviews/634561 ","creator":{"public_name":"Ирина","id":411432,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksandr Litvinov","id":117182,"is_tutor":true},"id":147311,"body":"Такую конструкцию лучше не использовать, она не однозначна: `return node.className = newclass;`. Лучше сначала присвоить, а потом вернуть.\n\nА `undefined` у вас появляется в том случае, если условие `if (node.className === oldclass)` не выполняется, дальше у вас нет никакого точного `return`, по умолчанию возвращается `undefined`.","topic_id":70148},{"creator":{"public_name":"Ирина","id":411432,"is_tutor":false},"id":147191,"body":"Да, вы правы, в этом месте должно меняться название класса (старый класс должен меняться на новый).\nЯ не понимаю, почему с помощью оператора if, когда я проверяю значение ключа claasName, в случае нахождения там старого класса, я меняю значение ключа на новый, но у меня все равно выходит undefined\n\nОператор присваивания я добавила в строку return node.className = newclass;\nМое ревью: https://ru.hexlet.io/code_reviews/634561","topic_id":70148},{"creator":{"public_name":"Ирина","id":411432,"is_tutor":false},"id":146840,"body":"Теперь вроде присваиваю свойству .classname новый класс (newclass), но все равно выдает undefined. В чем может быть ошибка?\nмое ревью https://ru.hexlet.io/code_reviews/634561","topic_id":70148},{"creator":{"public_name":"Aleksandr Litvinov","id":117182,"is_tutor":true},"id":146913,"body":"В последнем код-ревью вижу такой вывод тестов:\n\n```\n - \"className\": \"new-class\",\n + \"className\": \"hexlet-community\",\n```\n\nДолжно ли меняться название класса в этом месте?","topic_id":70148},{"creator":{"public_name":"Ирина","id":411432,"is_tutor":false},"id":147190,"body":"Изменила код, напиcала операцию присваивания, но все рfвно выдает undefined\nРевью: https://ru.hexlet.io/code_reviews/634561","topic_id":70148},{"creator":{"public_name":"Aleksandr Litvinov","id":117182,"is_tutor":true},"id":146787,"body":"`return node.className === newclass;` здесь нет операции присваивания, есть только сравнение.","topic_id":70148}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}},{"id":67076,"title":"Добрый день, помогите стать на путь истинный. Класс меняется на любом уровне вложенности, дерево возвращается такое же как исходное, с измененным классом. Малость в тупике )\nhttps://ru.hexlet.io/code_reviews/587704","plain_title":"Добрый день, помогите стать на путь истинный. Класс меняется на любом уровне вложенности, дерево возвращается такое же как исходное, с измененным классом. Малость в тупике ) https://ru.hexlet.io/code_reviews/587704 ","creator":{"public_name":"Igor","id":256310,"is_tutor":false},"comments":[{"creator":{"public_name":"Igor","id":256310,"is_tutor":false},"id":140704,"body":"Есть! Спасибо вам за оперативную помощь, добрый человек)","topic_id":67076},{"creator":{"public_name":"Konstantin Kuzin","id":342114,"is_tutor":false},"id":140695,"body":"Здравствуйте,\n\nВы изменяете исходное дерево, поэтому тест не проходит. ","topic_id":67076},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":140789,"body":"Здорово, что удалось разобраться! ","topic_id":67076}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"HTML-дерево","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":1734,"slug":"js_trees_html_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## changeClass.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает на вход html-дерево и заменяет во всех узлах имя класса, имена классов передаются через параметры. Функция не должна мутировать исходное дерево.\n\n### Примеры\n\n```javascript\nimport changeClass from '../changeClass.js';\n\nconst tree = {\n name: 'div',\n type: 'tag-internal',\n className: 'hexlet-community',\n children: [\n {\n name: 'div',\n type: 'tag-internal',\n className: 'old-class',\n children: [],\n },\n {\n name: 'div',\n type: 'tag-internal',\n className: 'old-class',\n children: [],\n },\n ],\n};\n\nconst result = changeClass(tree, 'old-class', 'new-class');\n// Результат:\n// {\n// name: 'div',\n// type: 'tag-internal',\n// className: 'hexlet-community',\n// children: [\n// {\n// name: 'div',\n// type: 'tag-internal',\n// className: 'new-class',\n// children: [],\n// },\n// {\n// name: 'div',\n// type: 'tag-internal',\n// className: 'new-class',\n// children: [],\n// },\n// ],\n// }\n```\n\nСвойство `className` может содержать только одно имя класса\n","prepared_readme":"## changeClass.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает на вход html-дерево и заменяет во всех узлах имя класса, имена классов передаются через параметры. Функция не должна мутировать исходное дерево.\n\n### Примеры\n\n```javascript\nimport changeClass from '../changeClass.js';\n\nconst tree = {\n name: 'div',\n type: 'tag-internal',\n className: 'hexlet-community',\n children: [\n {\n name: 'div',\n type: 'tag-internal',\n className: 'old-class',\n children: [],\n },\n {\n name: 'div',\n type: 'tag-internal',\n className: 'old-class',\n children: [],\n },\n ],\n};\n\nconst result = changeClass(tree, 'old-class', 'new-class');\n// Результат:\n// {\n// name: 'div',\n// type: 'tag-internal',\n// className: 'hexlet-community',\n// children: [\n// {\n// name: 'div',\n// type: 'tag-internal',\n// className: 'new-class',\n// children: [],\n// },\n// {\n// name: 'div',\n// type: 'tag-internal',\n// className: 'new-class',\n// children: [],\n// },\n// ],\n// }\n```\n\nСвойство `className` может содержать только одно имя класса\n","has_solution":true,"entity_name":"HTML-дерево"},"units":[{"id":5468,"name":"theory","url":"/courses/js-trees/lessons/html/theory_unit"},{"id":5769,"name":"quiz","url":"/courses/js-trees/lessons/html/quiz_unit"},{"id":5524,"name":"exercise","url":"/courses/js-trees/lessons/html/exercise_unit"}],"links":[{"id":423465,"name":"Знакомство с HTML","url":"https://ru.hexlet.io/courses/html/lessons/first_step/theory_unit"},{"id":423466,"name":"Для чего два восклицательных знака перед выражением?","url":"https://ru.hexlet.io/qna/javascript/questions/chto-oznachaet-dva-vosklitsatelnyh-znaka-pered-vyrazheniem"},{"id":423467,"name":"Figma plugin API: diving into advanced algorithms & data structures\n","url":"https://evilmartians.com/chronicles/figma-plugin-api-dive-into-advanced-algorithms-and-data-structures\n"}],"ordered_units":[{"id":5468,"name":"theory","url":"/courses/js-trees/lessons/html/theory_unit"},{"id":5769,"name":"quiz","url":"/courses/js-trees/lessons/html/quiz_unit"},{"id":5524,"name":"exercise","url":"/courses/js-trees/lessons/html/exercise_unit"}],"id":2416,"slug":"html","state":"approved","name":"HTML-дерево","course_order":900,"goal":"Учимся работать с другими деревьями на примере разметки HTML","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Древовидные структуры встречаются в разных областях: генеалогическое древо, файловая система и т.д. В этом уроке мы познакомимся с деревом разметки HTML, которое постоянно встречается в веб-разработке.\n\n```html\n<html>\n <body>\n <h1>Сообщество</h1>\n <p>Общение между пользователями Хекслета</p>\n <hr>\n <input>\n <div class='hexlet-community'>\n <div class='text-xs-center'></div>\n <div class='fa fa-spinner'></div>\n </div>\n </body>\n</html>\n```\n\nКорнем является тег html. Важно отметить, что некоторые теги не могут иметь вложенные теги внутри себя, например, hr и input.\n\nПопробуем описать это дерево в такой структуре, с которой было бы удобно работать. Первым шагом необходимо описать для каждого тега свойства, которыми он обладает. Как минимум можно выделить такие свойства: имя, тип, класс, дети. В реальности таких свойств бывает гораздо больше, но сейчас нам достаточно этих. Теперь опишем дерево html в этой структуре:\n\n```javascript\nconst htmlTree = {\n name: 'html',\n type: 'tag-internal',\n children: [\n {\n name: 'body',\n type: 'tag-internal',\n children: [\n {\n name: 'h1',\n type: 'tag-internal',\n children: [\n {\n type: 'text',\n content: 'Сообщество',\n },\n ],\n },\n {\n name: 'p',\n type: 'tag-internal',\n children: [\n {\n type: 'text',\n content: 'Общение между пользователями Хекслета',\n },\n ],\n },\n {\n name: 'hr',\n type: 'tag-leaf',\n },\n {\n name: 'input',\n type: 'tag-leaf',\n },\n {\n name: 'div',\n type: 'tag-internal',\n className: 'hexlet-community',\n children: [\n {\n name: 'div',\n type: 'tag-internal',\n className: 'text-xs-center',\n children: [],\n },\n {\n name: 'div',\n type: 'tag-internal',\n className: 'fa fa-spinner',\n children: [],\n },\n ],\n },\n ],\n },\n ],\n}\n```\n\nГлавным свойством в каждом узле является тип узла. В нашем дереве есть теги и текст. Текст может быть вложен в тег, то есть может быть потомком. Поэтому текст является листовым узлом. Также у нас есть некоторые теги, которые являются листовыми узлами. Поэтому для тегов выделено два типа: `tag-internal` — внутренние узлы, это теги, которые могут иметь детей; `tag-leaf` — листовые узлы, это теги, которые не могут иметь детей. Итак, для описания нашего дерева html достаточно определить три типа узлов:\n\n- `tag-internal` - теги, которые могут иметь детей, внутренний узел\n- `tag-leaf` - теги, которые не могут иметь детей, листовой узел\n- `text` - простой текст, листовой узел\n\nТеперь мы можем работать с нашим деревом. Например, отфильтруем все пустые теги. Для этого прежде всего надо определить, как фильтровать каждый тип. Каждый тип фильтруется по-своему:\n\n- `tag-internal` - если нет детей или все дети пустые, значит и родитель пустой\n- `tag-leaf` - не может иметь детей, такой тег всегда выводится\n- `text` - текстовый узел не может содержать детей, вместо этого он может содержать текстовый контент, поэтому фильтруем по пустому контенту\n\nФункция фильтрации будет выглядеть следующим образом:\n\n```javascript\nconst filterEmpty = (tree) => {\n const filtered = tree.children\n .map((node) => {\n // Перед фильтрацией отфильтровываем всех потомков\n if (node.type === 'tag-internal') {\n // Тут самый важный момент. Рекурсивно вызываем функцию фильтрации.\n // Дальнейшая работа не завершится, пока функция фильтрации не отфильтрует вложенные пустые узлы.\n return filterEmpty(node)\n }\n return node\n })\n .filter((node) => {\n const { type } = node\n // Каждый тип фильтруется по-своему, удобно для этого использовать switch\n switch (type) {\n case 'tag-internal': {\n // К этому моменту в текущем узле отфильтрованы потомки (остались только те, которые имеют своих детей)\n const { children } = node\n // Проверяем текущий узел, если он не пустой, возвращаем true (узел остается)\n return children.length > 0\n }\n case 'tag-leaf':\n // Листовые узлы всегда выводятся\n return true\n case 'text': {\n const { content } = node\n // Для текстовых узлов просто проверяем существование контента,\n return !!content // Для однозначности приводим значение к булевому типу\n }\n }\n })\n return { ...tree, children: filtered }\n}\n```\n\nФильтр в качестве параметра принимает узел с типом `tag-internal` и обрабатывает вложенные в него элементы. Вначале проходимся по всем потомкам и у всех, с типом `tag-internal`, фильтруем также вложенные элементы с помощью нашей же функции (рекурсия). Далее вызывается метод `filter()`, в нём каждый тип уже фильтруется по той логике, которую мы определили.\n\nПосле фильтрации получим такое дерево:\n\n```javascript\n{\n name: 'html',\n type: 'tag-internal',\n children: [\n {\n name: 'body',\n type: 'tag-internal',\n children: [\n {\n name: 'h1',\n type: 'tag-internal',\n children: [\n {\n name: '',\n type: 'text',\n content: 'Сообщество',\n },\n ],\n },\n {\n name: 'p',\n type: 'tag-internal',\n children: [\n {\n name: '',\n type: 'text',\n content: 'Общение между пользователями Хекслета',\n },\n ],\n },\n {\n name: 'hr',\n type: 'tag-leaf',\n },\n {\n name: 'input',\n type: 'tag-leaf',\n },\n ],\n },\n ],\n};\n```\n\nЭто дерево не содержит элемент `div` с классом `hexlet-community`, даже несмотря на то, что оно содержало другие элементы. Это произошло потому, что перед фильтрацией родителя были отфильтрованы его пустые потомки. Теперь можно собрать дерево в строку:\n\n```javascript\n// Для удобства определим отдельную функцию для формирования вывода класса\nconst buildClass = node => node.className ? ` class=${node.className}` : ''\n\n// Основная функция для сборки страницы\nconst buildHtml = (node) => {\n const { type, name } = node\n // Каждый тип формируется по-своему, как и в фильтрации используем switch\n switch (type) {\n case 'tag-internal': {\n // Этот тип может иметь детей, формируем вывод детей\n const childrenView = node.children.map(buildHtml).join('')\n // Собираем всё, вместе с родительским узлом\n return `<${name}${buildClass(node)}>${childrenView}</${name}>`\n }\n case 'tag-leaf':\n // Листовые узлы формируются просто\n return `<${name}${buildClass(node)}>`\n case 'text':\n // В текстовых узлах выводится сам контент\n return node.content\n }\n}\n\n// Получаем отфильтрованное дерево\nconst filteredTree = filterEmpty(htmlTree)\n\n// Формируем результат\nconst html = buildHtml(filteredTree)\nconsole.log(html) // => <html><body><h1>Сообщество</h1><p>Общение между пользователями Хекслета</p><hr><input></body></html>\n```\n\nЕсли расставить отступы и каждый тег на новой строке, то на выходе получаем html без пустых тегов:\n\n```html\n<html>\n <body>\n <h1>Сообщество</h1>\n <p>Общение между пользователями Хекслета</p>\n <hr>\n <input>\n </body>\n</html>\n```\n\nКод для обработки деревьев выглядит довольно лаконичным. Это - следствие удобной структуры для представления дерева html. Выделив в этом дереве несколько типов узлов, остаётся только описать логику для каждого типа. Любой внутренний узел является таким же деревом, поэтому обрабатывается рекурсивно этой же функцией. Правильное представление структуры значительно упрощает обработку дерева.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":2102,"name":"theory","url":"/courses/js-trees/lessons/intro/theory_unit"}],"links":[{"id":423443,"name":"Рекурсия, рекурсивный процесс и итеративный процесс","url":"https://ru.hexlet.io/blog/posts/recursive"}],"ordered_units":[{"id":2102,"name":"theory","url":"/courses/js-trees/lessons/intro/theory_unit"}],"id":1017,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом и его целями","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Дерево — одна из самых распространённых структур данных в информатике и естественный способ моделирования некоторых предметных областей. С деревьями (как структурой данных) встречаются так или иначе все люди, даже те, кто далёк не только от программирования, но и от компьютеров в целом. Самым очевидным примером служит генеалогическое древо, а из более специализированного — файловое дерево. HTML (как и JSON, XML и многие другие) также имеет древовидную структуру. Комментарии и каталоги продуктов на сайтах тоже бывают древовидными. Любая иерархия является деревом по определению.\n\n\n\nС деревьями связан один очень интересный аспект. Уровень понимания темы деревьев и способность с ними работать невероятно сильно коррелирует с уровнем разработчика. Если разработчику легко работать с деревьями, то, как правило, он довольно хорошо разбирается в коде, в том числе чужом, если нет, то и, в целом, у него больше сложностей с написанием и анализом кода.\n\nВ этом курсе нет нового синтаксиса и каких-то элементов программирования, которые не изучались на Хекслете до этого курса. Однако тема деревьев сложнее остальных тем из-за рекурсивной природы самих деревьев. Нужно \"повернуть\" мозги в правильную сторону и это, пожалуй, самая тяжёлая часть, которую невозможно \"прокачать\", читая теорию. В этом поможет только практика и эксперименты.\n\nДля упрощения процесса понимания и запоминания рекомендации такие же как и раньше:\n\n1. Обязательно повторяйте весь код, который даётся в теории, локально на своём компьютере.\n1. Используйте отладочную печать настолько, насколько можно. Выводите на экран все изменения данных во время работы кода.\n1. Повторите уроки из курса «JS: Функции» про рекурсию.\n\nВ этом небольшом курсе мы слегка погрузимся в тему деревьев и научимся с ними работать. Чего не будет в этом курсе, так это алгоритмов в том виде, в котором эта тема подаётся в университете. У данного курса совсем другие цели. Он учит работать с рекурсивными структурами данных через древовидную рекурсию.\n"},"id":155,"slug":"js-trees","challenges_count":8,"name":"JS: Деревья","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите работу с деревьями в JavaScript. Вы узнаете о рекурсивных структурах данных, обходе в глубину и древовидной рекурсии. В итоге вы разберетесь, как деревья устроены изнутри. Эти знания пригодятся при работе с категориями товаров, комментариями, HTML, XML, JSON.","kind":"advanced","updated_at":"2026-01-20T11:43:07.236Z","language":"javascript","duration_cache":38700,"skills":["Разбираться в принципах работы с деревьями","Представлять и обрабатывать данные с рекурсивной природой — например, файловые системы, каталоги или деревья комментариев","Строить абстракции поверх древовидных структур данных","Использовать функциональную парадигму"],"keywords":["рекурсивные структуры данных","обход в глубину","древовидная рекурсия","файловая система"],"lessons_count":9,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjcwMCwicHVyIjoiYmxvYl9pZCJ9fQ==--51548486d811e69f0ceadeb98be959da0409dd64/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[{"stack":{"id":12,"slug":"frontend","title":"Фронтенд-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":20,"duration_in_months":10},"id":17,"slug":"frontend","title":"Фронтенд-разработчик","subtitle":"Изучите HTML, CSS, JavaScript и React","subtitle_for_lists":"Изучите HTML, CSS, JavaScript и React","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"frontend","price_text":"от 6 792 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNywicHVyIjoiYmxvYl9pZCJ9fQ==--2d5cbbf5c3b4a73ae4b2c50632305d78f5872e4d/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"},{"stack":{"id":13,"slug":"backend","title":"Node.js-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":130,"duration_in_months":10},"id":19,"slug":"backend","title":"Node.js-разработчик","subtitle":"Изучите JavaScript, Node.js, Fastify и REST API","subtitle_for_lists":"Изучите JavaScript, Node.js, Fastify и REST API","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"backend","price_text":"от 4 755 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--2e84f5f94140ee4e22019ac479c290ef48c3fac8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"},{"stack":{"id":43,"slug":"fullstack-javascript","title":"Fullstack-разработчик на Node.js","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":140,"duration_in_months":12},"id":74,"slug":"fullstack-javascript","title":"Fullstack-разработчик на Node.js","subtitle":"Освоите JavaScript, Node.js, Fastify и React для фронтенда и бэкенда.","subtitle_for_lists":"Освоите JavaScript, Node.js, Fastify и React для фронтенда и бэкенда.","locale":"ru","current":true,"duration_in_months_text":"12 месяцев","stack_slug":"fullstack-javascript","price_text":"от 7 934 ₽","duration_text":"12 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--e2c6c0775e2308e42fbc5dc592ba2db0470632ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/js-trees/lessons/html/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">JS: Деревья</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: HTML-дерево</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"HTML-дерево","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"JS: Деревья"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Древовидные структуры встречаются в разных областях: генеалогическое древо, файловая система и т.д. В этом уроке мы познакомимся с деревом разметки HTML, которое постоянно встречается в веб-разработке.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><html>
<body>
<h1>Сообщество</h1>
<p>Общение между пользователями Хекслета</p>
<hr>
<input>
<div class='hexlet-community'>
<div class='text-xs-center'></div>
<div class='fa fa-spinner'></div>
</div>
</body>
</html></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Корнем является тег html. Важно отметить, что некоторые теги не могут иметь вложенные теги внутри себя, например, hr и input.</p>
<p>Попробуем описать это дерево в такой структуре, с которой было бы удобно работать. Первым шагом необходимо описать для каждого тега свойства, которыми он обладает. Как минимум можно выделить такие свойства: имя, тип, класс, дети. В реальности таких свойств бывает гораздо больше, но сейчас нам достаточно этих. Теперь опишем дерево html в этой структуре:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const htmlTree = {
name: 'html',
type: 'tag-internal',
children: [
{
name: 'body',
type: 'tag-internal',
children: [
{
name: 'h1',
type: 'tag-internal',
children: [
{
type: 'text',
content: 'Сообщество',
},
],
},
{
name: 'p',
type: 'tag-internal',
children: [
{
type: 'text',
content: 'Общение между пользователями Хекслета',
},
],
},
{
name: 'hr',
type: 'tag-leaf',
},
{
name: 'input',
type: 'tag-leaf',
},
{
name: 'div',
type: 'tag-internal',
className: 'hexlet-community',
children: [
{
name: 'div',
type: 'tag-internal',
className: 'text-xs-center',
children: [],
},
{
name: 'div',
type: 'tag-internal',
className: 'fa fa-spinner',
children: [],
},
],
},
],
},
],
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Главным свойством в каждом узле является тип узла. В нашем дереве есть теги и текст. Текст может быть вложен в тег, то есть может быть потомком. Поэтому текст является листовым узлом. Также у нас есть некоторые теги, которые являются листовыми узлами. Поэтому для тегов выделено два типа: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-internal</code> — внутренние узлы, это теги, которые могут иметь детей; <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-leaf</code> — листовые узлы, это теги, которые не могут иметь детей. Итак, для описания нашего дерева html достаточно определить три типа узлов:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-internal</code> - теги, которые могут иметь детей, внутренний узел</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-leaf</code> - теги, которые не могут иметь детей, листовой узел</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">text</code> - простой текст, листовой узел</li>
</ul>
<p>Теперь мы можем работать с нашим деревом. Например, отфильтруем все пустые теги. Для этого прежде всего надо определить, как фильтровать каждый тип. Каждый тип фильтруется по-своему:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-internal</code> - если нет детей или все дети пустые, значит и родитель пустой</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-leaf</code> - не может иметь детей, такой тег всегда выводится</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">text</code> - текстовый узел не может содержать детей, вместо этого он может содержать текстовый контент, поэтому фильтруем по пустому контенту</li>
</ul>
<p>Функция фильтрации будет выглядеть следующим образом:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const filterEmpty = (tree) => {
const filtered = tree.children
.map((node) => {
// Перед фильтрацией отфильтровываем всех потомков
if (node.type === 'tag-internal') {
// Тут самый важный момент. Рекурсивно вызываем функцию фильтрации.
// Дальнейшая работа не завершится, пока функция фильтрации не отфильтрует вложенные пустые узлы.
return filterEmpty(node)
}
return node
})
.filter((node) => {
const { type } = node
// Каждый тип фильтруется по-своему, удобно для этого использовать switch
switch (type) {
case 'tag-internal': {
// К этому моменту в текущем узле отфильтрованы потомки (остались только те, которые имеют своих детей)
const { children } = node
// Проверяем текущий узел, если он не пустой, возвращаем true (узел остается)
return children.length > 0
}
case 'tag-leaf':
// Листовые узлы всегда выводятся
return true
case 'text': {
const { content } = node
// Для текстовых узлов просто проверяем существование контента,
return !!content // Для однозначности приводим значение к булевому типу
}
}
})
return { ...tree, children: filtered }
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Фильтр в качестве параметра принимает узел с типом <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-internal</code> и обрабатывает вложенные в него элементы. Вначале проходимся по всем потомкам и у всех, с типом <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tag-internal</code>, фильтруем также вложенные элементы с помощью нашей же функции (рекурсия). Далее вызывается метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">filter()</code>, в нём каждый тип уже фильтруется по той логике, которую мы определили.</p>
<p>После фильтрации получим такое дерево:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">{
name: 'html',
type: 'tag-internal',
children: [
{
name: 'body',
type: 'tag-internal',
children: [
{
name: 'h1',
type: 'tag-internal',
children: [
{
name: '',
type: 'text',
content: 'Сообщество',
},
],
},
{
name: 'p',
type: 'tag-internal',
children: [
{
name: '',
type: 'text',
content: 'Общение между пользователями Хекслета',
},
],
},
{
name: 'hr',
type: 'tag-leaf',
},
{
name: 'input',
type: 'tag-leaf',
},
],
},
],
};</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Это дерево не содержит элемент <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">div</code> с классом <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet-community</code>, даже несмотря на то, что оно содержало другие элементы. Это произошло потому, что перед фильтрацией родителя были отфильтрованы его пустые потомки. Теперь можно собрать дерево в строку:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// Для удобства определим отдельную функцию для формирования вывода класса
const buildClass = node => node.className ? ` class=${node.className}` : ''
// Основная функция для сборки страницы
const buildHtml = (node) => {
const { type, name } = node
// Каждый тип формируется по-своему, как и в фильтрации используем switch
switch (type) {
case 'tag-internal': {
// Этот тип может иметь детей, формируем вывод детей
const childrenView = node.children.map(buildHtml).join('')
// Собираем всё, вместе с родительским узлом
return `<${name}${buildClass(node)}>${childrenView}</${name}>`
}
case 'tag-leaf':
// Листовые узлы формируются просто
return `<${name}${buildClass(node)}>`
case 'text':
// В текстовых узлах выводится сам контент
return node.content
}
}
// Получаем отфильтрованное дерево
const filteredTree = filterEmpty(htmlTree)
// Формируем результат
const html = buildHtml(filteredTree)
console.log(html) // => <html><body><h1>Сообщество</h1><p>Общение между пользователями Хекслета</p><hr><input></body></html></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если расставить отступы и каждый тег на новой строке, то на выходе получаем html без пустых тегов:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><html>
<body>
<h1>Сообщество</h1>
<p>Общение между пользователями Хекслета</p>
<hr>
<input>
</body>
</html></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Код для обработки деревьев выглядит довольно лаконичным. Это - следствие удобной структуры для представления дерева html. Выделив в этом дереве несколько типов узлов, остаётся только описать логику для каждого типа. Любой внутренний узел является таким же деревом, поэтому обрабатывается рекурсивно этой же функцией. Правильное представление структуры значительно упрощает обработку дерева.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/frontend?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Фронтенд-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите HTML, CSS, JavaScript и React</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNywicHVyIjoiYmxvYl9pZCJ9fQ==--2d5cbbf5c3b4a73ae4b2c50632305d78f5872e4d/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png" alt="Фронтенд-разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/backend?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Node.js-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите JavaScript, Node.js, Fastify и REST API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--2e84f5f94140ee4e22019ac479c290ef48c3fac8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png" alt="Node.js-разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 4 755 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/fullstack-javascript?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">12 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Fullstack-разработчик на Node.js</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Освоите JavaScript, Node.js, Fastify и React для фронтенда и бэкенда.</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--e2c6c0775e2308e42fbc5dc592ba2db0470632ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png" alt="Fullstack-разработчик на Node.js" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 7 934 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/js-trees/lessons/html/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 9</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/js-trees/lessons/html/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-CdBlNCiQ.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>