<!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 17:14:05 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="b1VkGXJl7qfvIz44LBWrEgDax5-08whBr9a20pD2EHKAhK8ugBtDx1lgGqAgGltlwNPqNbzE9uMSNiyGwvH3HA";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>Fluent interface | JS: Коллекции</title>
<meta name="description" content="Fluent interface / JS: Коллекции: Знакомимся с паттерном проектирования Fluent Interface">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-collections/lessons/fluent_interface/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Fluent interface">
<meta property="og:title" content="JS: Коллекции">
<meta property="og:description" content="Fluent interface / JS: Коллекции: Знакомимся с паттерном проектирования Fluent Interface">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-collections/lessons/fluent_interface/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="Vep21WUr0i1OsWYhhWVNG8Ou2mY_fv9QsQPojwjjUPW6O73il1V_TfjyQrmJar1sA6f3zDdJAfIM43LbWuS3mw" />
<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/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-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-26T17:14:05.478Z","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":"MaAATX_ljf179TWg4KIzgrytMXA8OMMFP4U-Zgr-BT3ecct6jZsgnc22ETjsrcP1fKQc2jQPPaeCZaQyWPniUw","topics":[{"id":26368,"title":"```\ntoArray() {\n return this.collection.slice();\n }\n```\nЗачем надо возвращать копию коллекции? Нельзя просто вернуть коллекцию?","plain_title":"toArray() { return this.collection.slice(); } Зачем надо возвращать копию коллекции? Нельзя просто вернуть коллекцию? ","creator":{"public_name":"Alex Sandr","id":67099,"is_tutor":false},"comments":[{"creator":{"public_name":"Alexander Perio","id":207567,"is_tutor":false},"id":62799,"body":"**Сергей К.**,\n\n```\ntoArray() {\n return this.collection;\n }\n```\n\nтак тоже проходит проверку\n\nили суть в том, чтобы создать копию для того, чтобы избежать мутации?","topic_id":26368},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":56418,"body":"Алекс, приветствую! А вы пробовали так поступить?","topic_id":26368}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":48461,"title":"Мне не понятно в чем разница, между this и this.collection. И зачем нам возвращать this? Почему нельзя использовать как просто методы объекта без всякого возврата this? И так же через точку можно запускать.","plain_title":"Мне не понятно в чем разница, между this и this.collection. И зачем на возвращать this? Почему нельзя использовать как просто методы объекта без всякого возврата this? И так же через точку можно запускать. ","creator":{"public_name":"Владимир","id":295128,"is_tutor":false},"comments":[{"creator":{"public_name":"Ruslan Knyazev","id":150888,"is_tutor":true},"id":104082,"body":"**Владимир**, приветствую.\nthis в контексте наших методов это сам объект, this.collection опять же, в контексте этих методов это свойство collection текущего объекта.\nСмысл возврата this в том, что он нужен для fluent interface. Да, без возврата this тоже можно запускать через точку, но только 1 раз, а не сколько нам нужно раз до финального результата.","topic_id":48461}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":41580,"title":"Как по мне, то метод select не должен мутировать исходный объект, а должен возвращать новый экземпляр типа Enumerable.","plain_title":"Как по мне, то метод select не должен мутировать исходный объект, а должен возвращать новый экземпляр типа Enumerable. ","creator":{"public_name":"Vyacheslav","id":240737,"is_tutor":false},"comments":[{"creator":{"public_name":"Vyacheslav","id":240737,"is_tutor":false},"id":90598,"body":"Вопрос решился сам собой после просмотра следующей темы.","topic_id":41580}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":7159,"title":"Помогите, пожалуйста, понять как работает код. У меня есть функция:\n```\norderBy(fn, way) {\n this.collection = this.collection.filter(fn);\n return this;\n }\n```\nв тесте я прописал \n```\nconst test = coll.orderBy(car => car.year);\nconsole.log(\"orderBy: \" + test.toArray());\n```\nна выходе я получаю `orderBy: sorento,rio,sportage`. Почему так происходит? Где года?","plain_title":"Помогите, пожалуйста, понять как работает код. У меня есть функция: orderBy(fn, way) { this.collection = this.collection.filter(fn); return this; } в тесте я прописал const test = coll.orderBy(car => car.year); console.log(\"orderBy: \" + test.toArray()); на выходе я получаю orderBy: sorento,rio,sportage. Почему так происходит? Где года? ","creator":{"public_name":"Slava","id":124445,"is_tutor":false},"comments":[{"creator":{"public_name":"Константин Бочинин","id":43218,"is_tutor":false},"id":14157,"body":"Спасибо, добрый человек!","topic_id":7159},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":13257,"body":"Кстати, Вы можете делать \"экспресс-проверку\" в терминале практики, набирая там команду `make test`","topic_id":7159},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":13256,"body":"> У меня есть функция\n\n`orderBy` - это функция, предназначенная для сортировки. Внутри неё вы применяете к коллекции операцию фильтрации (`filter`), но сортировка и фильтрация - совершенно разные операции.\n\n> в тесте я прописал\n\nвспомните, как работает функция `filter`, она принимает на вход функцию, которая в итоге должна возвращать булево значение. В данном случае всегда будет возвращаться значение года объекта, которое будет в большинстве случаев (если оно, например, вообще существует и не равно нулю) трактоваться в логическом контексте как `true`.\n\n> Почему так происходит? Где года?\n\nКаково было состояние `coll` до вызова `orderBy`? `coll` используют (и меняют) разные тесты, и многое зависит от того, каким образом и в каком месте тестов вы прописали этот код","topic_id":7159}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":29430,"title":"Не могу понять как сделать функцию orderBy. Она первым параметром принимает функцию car => car.year, не могу понять, что с этой функцией нужно делать. Если бы первый параметром передавалась не функция, а ключ, то можно было бы сделать так \n\n`// removed`","plain_title":"Не могу понять как сделать функцию orderBy. Она первым параметром принимает функцию car => car.year, не могу понять, что с этой функцией нужно делать. Если бы первый параметром передавалась не функция, а ключ, то можно было бы сделать так // removed ","creator":{"public_name":"Андрей Константинов","id":199156,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":63776,"body":"Отлично!","topic_id":29430},{"creator":{"public_name":"Андрей Константинов","id":199156,"is_tutor":false},"id":63706,"body":"Разобрался, что параметры функции sort((a,b) => ...) нужно передавать результат вызова fn к этим параметрам.","topic_id":29430},{"creator":{"public_name":"Андрей Константинов","id":199156,"is_tutor":false},"id":63723,"body":"Сейчас разбираюсь с тем как сделать так, чтобы sort нормально работал и с числами и со строками. Подскажите, пожалуйста, нужно копать в условие для sort или нужно где-то вставить проверку?","topic_id":29430},{"creator":{"public_name":"Андрей Константинов","id":199156,"is_tutor":false},"id":63748,"body":"Решено ","topic_id":29430},{"creator":{"public_name":"Павел Ким","id":50933,"is_tutor":false},"id":63702,"body":"Попробуйте распечатать a и b, которые передаются в функцию sort. Что это за значения?","topic_id":29430}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":29536,"title":"Добрый день!\nМоё решение имеет право жить? ) Или есть минусы?\nhttps://ru.hexlet.io/code_reviews/124921","plain_title":"Добрый день! Моё решение имеет право жить? ) Или есть минусы? https://ru.hexlet.io/code_reviews/124921 ","creator":{"public_name":"Михаил М","id":230054,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":64015,"body":"Приветствую, Михаил! Чтобы получить обратную связь по коду, вам надо попробовать сформулировать конкретный вопрос, что вас интересует, вызывает сомнение или кажется непонятным? Также обязательно сопоставьте ваше решение с учительским, проанализируйте его.","topic_id":29536}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":49997,"title":"Здравствуйте, помогите пожалуйста разобраться с сортировкой.\nТема урока мне понятна, а реализация самого метода \nнетhttps://ru.hexlet.io/u/user-7f9676e0338be25a","plain_title":"Здравствуйте, помогите пожалуйста разобраться с сортировкой. Тема урока мне понятна, а реализация самого метода нетhttps://ru.hexlet.io/u/user-7f9676e0338be25a ","creator":{"public_name":"Татьяна Чернышова","id":309206,"is_tutor":false},"comments":[{"creator":{"public_name":"Татьяна Чернышова","id":309206,"is_tutor":false},"id":107262,"body":" спасибо большое","topic_id":49997},{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":107191,"body":"**Татьяна**, приветствую.\n\nФункция сортировки должна возвращать одно из трёх чисел - 0, 1 и -1. При обратной сортировки знак минуса просто переносится в другое условие. Здесь вам нужно как раз реализовать эту логику. Чтобы сравнивать значения (получить a и b) необходимо их получить из функции, которую передают в метод.\n\nPS\n\nВ качестве ссылки на ревью нужно прикреплять ссылку на него.","topic_id":49997}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":28568,"title":"Добрый день.\n\nНет ли ошибки на слайде DSL? Должна ли строка ниже заканчиваться точкой с запятой? \n\n`.select(car => car.model);`\n","plain_title":"Добрый день. Нет ли ошибки на слайде DSL? Должна ли строка ниже заканчиваться точкой с запятой? .select(car => car.model); ","creator":{"public_name":"A","id":229451,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":61599,"body":"Да, обязательно поправим, когда будем рефакторить курс. Спасибо!","topic_id":28568}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":10237,"title":"Добрый день подскажите сортировка по возрастанию для строк\nдолжна же работать ```sort((a,b) => fn(a) - fn(b))```или все же я не правильно документацию понял ?","plain_title":"Добрый день подскажите сортировка по возрастанию для строк должна же работать sort((a,b) => fn(a) - fn(b))или все же я не правильно документацию понял ? ","creator":{"public_name":"Дмитрий Антонов","id":36638,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":21133,"body":"Здравствуйтe! В соответствии с документацией, `compareFunction` должна возвращать одно из трёх значений: 1, 0 или -1. Какие значения возвращает ваша реализация `compareFunction` при сравнении строк?","topic_id":10237},{"creator":{"public_name":"Дмитрий Антонов","id":36638,"is_tutor":false},"id":21321,"body":"Спасибо, правда уже не помню что выдавало... но уже решил.","topic_id":10237}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}},{"id":15963,"title":"Добрый день!\n\nРешение отличается от решения учителя. [Ревью](https://ru.hexlet.io/code_reviews/50314)\nМожно ли мое решение считать альтернативой решению учителя, или возможно есть недостатки, которые нужно учесть и исправить?","plain_title":"Добрый день! Решение отличается от решения учителя. Ревью (https://ru.hexlet.io/code_reviews/50314) Можно ли мое решение считать альтернативой решению учителя, или возможно есть недостатки, которые нужно учесть и исправить? ","creator":{"public_name":"Контантин Булгаков","id":172477,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":33751,"body":"У вас orderBy, в принципе, не верно сравнение делает. Посмотрите в официальной документации как правильно делать сравнение (в решении учителя именно такой вариант).","topic_id":15963},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":33749,"body":"Здравствуйте! У вас чуть более сложное (нагромождённое) решение, в учительском решении одинаково обрабатываются и строки и числа, у вас же для этого специально была выделена ветка if","topic_id":15963}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Fluent interface","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":474,"slug":"js_collections_fluent_interface_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## Enumerable.js\n\n**Select**\n\nРеализуйте метод `select()`, который отображает (принцип работы как у функции `map()`)\nколлекцию, другими словами, извлекает из элементов коллекции нужные данные и возвращает объект с новой (отображенной) коллекцией из этих данных.\n\n```javascript\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia', model: 'sorento', year: 2014 },\n { brand: 'kia', model: 'rio', year: 2010 },\n { brand: 'kia', model: 'sportage', year: 2012 },\n]\nconst coll = new Enumerable(cars)\n\n// [car] => [model]\nconst result = coll.select(car => car.model)\n\nassert.deepEqual(result.toArray(), ['m5', 'm4', 'sorento', 'rio', 'sportage'])\n```\n\n**OrderBy**\n\nРеализуйте метод `orderBy()`, который сортирует коллекцию на основе переданных данных.\n\nПринимаемые параметры:\n\n1. Функция, возвращающая значение, по которому будет происходить сортировка.\n1. Направление сортировки: `asc` — по возрастанию, `desc` — по убыванию (по умолчанию — `asc`). Если передан некорректный параметр, то для сортировки используется значение по умолчанию.\n\n```javascript\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia', model: 'sorento', year: 2014 },\n { brand: 'kia', model: 'rio', year: 2010 },\n { brand: 'kia', model: 'sportage', year: 2012 },\n]\ncoll = new Enumerable(cars)\n\nconst result = coll.orderBy(car => car.year, 'desc')\n .where(car => car.brand === 'bmw')\n .select(car => car.model)\n\nassert.deepEqual(result.toArray(), ['m5', 'm4'])\n```\n\n### Подсказки\n\nДля выполнения сортировки воспользуйтесь встроенной функцией: [sort()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).\n","prepared_readme":"## Enumerable.js\n\n**Select**\n\nРеализуйте метод `select()`, который отображает (принцип работы как у функции `map()`)\nколлекцию, другими словами, извлекает из элементов коллекции нужные данные и возвращает объект с новой (отображенной) коллекцией из этих данных.\n\n```javascript\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia', model: 'sorento', year: 2014 },\n { brand: 'kia', model: 'rio', year: 2010 },\n { brand: 'kia', model: 'sportage', year: 2012 },\n]\nconst coll = new Enumerable(cars)\n\n// [car] => [model]\nconst result = coll.select(car => car.model)\n\nassert.deepEqual(result.toArray(), ['m5', 'm4', 'sorento', 'rio', 'sportage'])\n```\n\n**OrderBy**\n\nРеализуйте метод `orderBy()`, который сортирует коллекцию на основе переданных данных.\n\nПринимаемые параметры:\n\n1. Функция, возвращающая значение, по которому будет происходить сортировка.\n1. Направление сортировки: `asc` — по возрастанию, `desc` — по убыванию (по умолчанию — `asc`). Если передан некорректный параметр, то для сортировки используется значение по умолчанию.\n\n```javascript\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia', model: 'sorento', year: 2014 },\n { brand: 'kia', model: 'rio', year: 2010 },\n { brand: 'kia', model: 'sportage', year: 2012 },\n]\ncoll = new Enumerable(cars)\n\nconst result = coll.orderBy(car => car.year, 'desc')\n .where(car => car.brand === 'bmw')\n .select(car => car.model)\n\nassert.deepEqual(result.toArray(), ['m5', 'm4'])\n```\n\n### Подсказки\n\nДля выполнения сортировки воспользуйтесь встроенной функцией: [sort()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).\n","has_solution":true,"entity_name":"Fluent interface"},"units":[{"id":1413,"name":"theory","url":"/courses/js-collections/lessons/fluent_interface/theory_unit"},{"id":1415,"name":"quiz","url":"/courses/js-collections/lessons/fluent_interface/quiz_unit"},{"id":1414,"name":"exercise","url":"/courses/js-collections/lessons/fluent_interface/exercise_unit"}],"links":[],"ordered_units":[{"id":1413,"name":"theory","url":"/courses/js-collections/lessons/fluent_interface/theory_unit"},{"id":1415,"name":"quiz","url":"/courses/js-collections/lessons/fluent_interface/quiz_unit"},{"id":1414,"name":"exercise","url":"/courses/js-collections/lessons/fluent_interface/exercise_unit"}],"id":711,"slug":"fluent_interface","state":"approved","name":"Fluent interface","course_order":500,"goal":"Знакомимся с паттерном проектирования Fluent Interface","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"174202546","theory":"В программировании существует такое понятие, как паттерны проектирования. Это некоторые решения типичных задач, которые кто-то увидел, обобщил и придумал некий подход, подходящий для определенного типа задач. В этом уроке мы разберем Fluent Interface, который является одним из примеров таких паттернов.\n\n## Что такое Fluent Interface\n\nДля начала посмотрим, как выглядит наш DSL, и узнаем, что в нем является Fluent Interface:\n\n```javascript\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia', model: 'sorento', year: 2014 },\n { brand: 'kia', model: 'rio', year: 2010 },\n]\n\n// Создаем некоторую коллекцию и оборачиваем в класс Enumerable наш cars\nconst coll = new Enumerable(cars)\n// Применяем различные методы, которые производят некоторые изменения с машинами\n// Делаем сортировку\nconst result = coll.orderBy(car => car.year)\n // Делаем фильтрацию\n .where(car => car.brand === 'kia')\n // Делаем выборку определенных полей изнутри\n .select(car => car.model)\n // Получаем новый массив, в котором видим, что выбирали и как\n .toArray()\n// ['rio', 'sorento' ]\n```\n\nFluent Interface здесь заключается в том, что мы постоянно через точку продолжаем вызовы. В нашем примере идет вызов метода, а после этого точка, и это повторяется. Эти элементы кода могут вызываться бесконечно друг за другом, но бывают методы, которые являются окончательными.\n\nНапример, `toArray` возвращает конкретный массив, и после него уже нельзя вызвать `where`. Поэтому методы бывают разные. Некоторые могут продолжать этот интерфейс, некоторые не могут.\n\nВ определении специалистов в области объектно-ориентированного программирования **Fluent Interface** — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.\n\nЭто не является прерогативой объектно-ориентированного программирования. Хотя из-за того, как вызываются методы, этот подход здесь выглядит именно так.\n\nВ функциональном программировании обычно используется чуть более мощная стратегия, которая называется **функциональная композиция**. В таком случае код остается неизменяемым, у нас будут чистые функции, и при этом она позволяет делать чуть более крутые вещи. Но это выходит за рамки этого урока. Главное понимать, что функциональная композиция и fluent interface имеют сходство.\n\nНиже приведен код на jQuery — это пример популярного DSL, который позволяет читабельно описывать то, что мы хотим сделать со страницей:\n\n```javascript\n$(this).find('img').stop()\n .animate({ opacity: 0.8 }, 300).end()\n .find('.viewcasestudy').fadeIn('fast')\n```\n\n## TDD\n\nТеперь поработаем по TDD. В данном случае библиотека простая, а код предсказуемый и детерминированный, поэтому все очень просто тестировать и писать:\n\n```javascript\n// Создаем тестовые данные\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia', model: 'sorento', year: 2014 },\n { brand: 'kia', model: 'rio', year: 2010 },\n { brand: 'kia', model: 'sportage', year: 2012 },\n]\n// Делаем оборачивание коллекции машин\nconst coll = new Enumerable(cars)\n\n// Делаем выборку\nconst result = coll\n .where(car => car.brand === 'kia')\n .where(car => car.year > 2011)\n\nassert.deepEqual(result.toArray(), [cars[2], cars[4]])\n```\n\nВ конце мы используем `assert`, но уже используем не `equal`, а `deepEqual`. Этот метод тоже проверяет равенство. В следующих уроках мы поймем, зачем это нужно.\n\nМы проверяем, что результирующий массив равен массиву `[cars[2], cars[4]]`. То есть машины под индексами 2 и 4 соответствуют параметрам выборки.\n\nЭто весь тест, который необходим, чтобы протестировать базовую функциональность нашей библиотеки. Осталось реализовать ее и посмотреть, как она устроена внутри.\n\n## Реализация\n\nРеализовываем библиотеку:\n\n```javascript\n// Определяем класс\nclass Enumerable {\n // Конструктор, который устанавливает коллекцию внутрь\n constructor(collection) {\n this.collection = collection\n }\n\n where(fn) {\n // Присваиваем новую коллекцию — полученную фильтрацию предыдущей\n // По сути одну коллекцию заменяем на другую, пропуская через фильтр\n this.collection = this.collection.filter(fn)\n return this\n }\n\n toArray() {\n return this.collection\n }\n}\n```\n\nНа выходе из функции `where` мы производим действие, которое дает возможность использовать Fluent interface. Мы делаем `return this`.\n\n`this` — это объект, с которым мы работаем в данный момент. Поэтому на выходе у нас получается тот же объект, из которого можно дальше вызывать эти же методы до тех пор, пока один из них не перестанет возвращать `this`. Это всё, что нужно для Fluent interface.\n\nВ конце реализации стоит `toArray()`, который возвращает саму коллекцию. Больше никаких преобразований здесь нет. Поэтому реализация Fluent interface — очень простая.\n\nДля большего понимания этого, разложим еще один пример:\n\n```javascript\nconst result = coll.orderBy(car => car.year)\n\nresult.where(car => car.brand === 'kia')\nresult.select(car => car.model)\nresult.toArray()\n```\n\nДопустим, мы берем коллекцию и делаем `orderBy`. Она возвращает `this` — это та же коллекция. Получается, что в `result` лежит `this` — `coll`, только уже преобразованный. То есть мы получаем ссылку на то, что лежало в `coll`. И после этого мы продолжаем вызывать методы.\n\nНесмотря на то, что `where` заменяет одну коллекцию на другую, он делает это в рамках одного объекта. В итоге получается, что мы мутируем объект каждый раз, когда вызываем такие методы.\n\n## Выводы\n\nВ этом уроке мы познакомились с паттерном проектирования Fluent Interface. Мы узнали, что в определении специалистов в области объектно-ориентированного программирования **Fluent Interface** — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.\n\nТакже узнали, что в функциональном программировании обычно используется **функциональная композиция**, что имеет сходство с Fluent Interface.\n\nЕще нам удалось протестировать базовую функциональность нашей библиотеки и реализовать ее. Также мы изучили, как она устроена внутри.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1404,"name":"theory","url":"/courses/js-collections/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":1404,"name":"theory","url":"/courses/js-collections/lessons/intro/theory_unit"}],"id":707,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с целями курса и обсуждаем проект, над которым мы будем работать","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"174202449","theory":"В предыдущих курсах мы уже научились работать с составными данными и поняли, зачем они нужны. Также мы изучили концептуальные способы работы, которые не зависят от конкретного языка программирования.\n\nВ этом курсе мы разберем конкретные приемы, которые используются для работы с коллекциями в языке JavaScript. При этом они будут подходить и ко многим другим языкам.\n\n## Какие темы будем изучать\n\nСписки есть везде и их бывает много. Они окружают нас повсеместно. Например, в Хекслете есть списки:\n\n* Пользователей\n* Курсов\n* Уроков\n* Топиков\n* Комментариев\n\nЕще есть не связанные с конкретными вещами списки, которые вводятся на сайте. Они существуют только внутри кода.\n\nВ этом курсе мы изучим несколько новых типов данных: массивы, ассоциативные массивы и множества.\n\nТакже мы познакомимся со следующими темами:\n\n* **Передача параметров по ссылке и по значению** — более сложный механизм, чем мы изучали до этого\n* **Функции высшего порядка** — встроены в JavaScript и являются каноническим способом работы с коллекциями\n* **Spread- и rest-операции** — распространены в других языках программирования и позволяют сократить шаблонный код\n* **Destructing assignment** — деструктуризация. Техника, которая используется в функциональных программированиях и языках. Она часто соприкасается с другой техникой, которая называется pattern matching\n\nТакже в этом курсе мы познакомимся с новыми техниками программирования, которые не относятся напрямую к работе с коллекциями. Это будет связано с проектом, который мы будем делать.\n\nМы поговорим о техниках:\n\n* **Lazy Evaluation** — ленивые вычисления\n* **Memoization** — мемоизация\n* **Fluent interface** — текучий интерфейс\n\nЕще мы познакомимся с понятием **DSL — Domain-specific language**. Это специализированный язык для конкретной области применения. Здесь речь не о языке программирования, хотя это тоже возможно, но с некоторыми ограничениями.\n\nDSL делится на два типа:\n\n* **Внешний**. Сюда входят языки запросов SQL и XPath, языки разметки HTML и Markdown, регулярные выражения. Они не пишутся на целевом языке, а формируются как текст, который разбирается специальными инструментами\n* **Внутренний**. Сюда входит методика создания внутренних DSL — Fluent Interface. Внутренний DSL написан на целевом языке — на котором мы программируем. Этот DSL обычно является библиотекой. Ее API выглядит как естественный язык, при этом детали реализации спрятаны\n\nDSL — важный механизм, который позволяет создавать специализированные языки. Они уменьшают вероятность появления ошибок и позволяют эффективно и быстро выражать и формировать понятия для определенной предметной области.\n\n## С каким проектом будем работать\n\nВ этом курсе мы будем делать проект, который называется Linq. Это библиотека, которая пришла из мира .NET — из языка C-sharp. Она позволяет описывать, как мы хотим обработать коллекцию и выдавать результат.\n\nСразу рассмотрим пример:\n\n```javascript\nimport HexletLinq from 'hexlet-linq';\n\nconst cars = [\n { brand: 'bmw', model: 'm5', year: 2014 },\n { brand: 'bmw', model: 'm4', year: 2013 },\n { brand: 'kia' model: 'sorento', year: 2014},\n { brand: 'kia', model: 'rio', year: 2010 },\n { brand: 'kia', model: 'sportage', year: 2012 },\n];\nconst coll = HexletLinq.from(cars);\n```\n\nЗдесь мы формируем некоторую структуру — список машин. Каждая машина представлена некоторым объектом с определенным набором данных: бренд, модель и год выпуска.\n\nПосле этого с помощью нашей библиотеки мы формируем эту коллекцию через `HexletLinq.from(cars)` — здесь происходит оборачивание или враппинг массива.\n\nУ этой коллекции есть большое количество методов, которые позволяют ее обрабатывать.\n\nНапример, мы хотим получить все модели машин, отсортированных в обратном порядке по году и принадлежащие бренду Kia:\n\n```javascript\nconst result = coll.orderBy(car => car.year, 'desc')\n .where(car => car.brand === 'kia')\n .select(car => car.model).toArray()\n\n// [ 'sorento', 'sportage', 'rio' ]\n```\n\nЧтобы выполнить эту задачу, мы пишем код так, что он читается почти как английский язык. Мы говорим, что нужно отсортировать (order by) по году выпуска (year) в обратном порядке (desc). При этом нам нужно выбрать из этих машин только те, у которых бренд Kia (where brand kia).\n\nРезультат должен представлять не коллекцию машин, а коллекцию моделей. Поэтому достаем из машин их модели (select model).\n\nДальше у нас есть специальный метод `toArray`, который преобразует список к обычному массиву, и на выходе получаем `[ 'sorento', 'sportage', 'rio' ]`. Массив содержит все модели бренда Kia, которые отсортированы по году в обратном порядке.\n\n## Почему этот курс\n\nКакие преимущества есть при изучении этого курса:\n\n* Будем писать настоящий канонический JS-код с помощью функции высших порядков. Это код, который в реальности пишут в продакшене\n* Будем обучаться через рефакторинг, а код будет эволюционировать. Значит, проект будет становиться лучше — меняться по урокам\n* Будем разрабатывать проект через тесты\n* Нужно будет думать и включаться в каждое упражнение, которое мы предоставляем\n"},"id":124,"slug":"js-collections","challenges_count":11,"name":"JS: Коллекции","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите конкретные приемы в работе с коллекциями в языке JavaScript. Вы узнаете больше о массивах, ассоциативных массивах и множествах. В итоге вы научитесь представлять данные в виде множеств с помощью Set, использовать Map для создания словарей, создавать ленивые коллекции для уменьшения количества проходов и мемоизировать вызовы функций для оптимизации производительности. Работа с коллекциями в JavaScript пригодится, если вы решите оптимизировать производительность своего кода. Знания из этого курса помогут программистам ускорить и упростить работу с данными.","kind":"additional","updated_at":"2026-01-20T11:40:19.676Z","language":"javascript","duration_cache":48240,"skills":["Представлять данные в виде множеств с помощью Set","Использовать Map для создания словарей","Создавать ленивые коллекции для уменьшения количества проходов","Мемоизировать вызовы функций для оптимизации производительности"],"keywords":["Set","Map","текучий интерфейс","ленивые коллекции","мемоизация"],"lessons_count":11,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjM4OSwicHVyIjoiYmxvYl9pZCJ9fQ==--d301339bc142b8588582eee69ca033648170d5c2/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[{"stack":{"id":20,"slug":"js-sicp","title":"СИКП на JS","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4050,"duration_in_months":1},"id":28,"slug":"js-sicp","title":"СИКП на JS","subtitle":"Навык понимать программы на фундаментальном уровне, уверенно проходить собеседования и решать сложные задачи","subtitle_for_lists":"Навык фундаментального программирования","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"js-sicp","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/js-collections/lessons/fluent_interface/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">Теория: Fluent interface</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Fluent interface","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>В программировании существует такое понятие, как паттерны проектирования. Это некоторые решения типичных задач, которые кто-то увидел, обобщил и придумал некий подход, подходящий для определенного типа задач. В этом уроке мы разберем Fluent Interface, который является одним из примеров таких паттернов.</p>
<h2 id="heading-2-1">Что такое Fluent Interface</h2>
<p>Для начала посмотрим, как выглядит наш DSL, и узнаем, что в нем является Fluent Interface:</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 cars = [
{ brand: 'bmw', model: 'm5', year: 2014 },
{ brand: 'bmw', model: 'm4', year: 2013 },
{ brand: 'kia', model: 'sorento', year: 2014 },
{ brand: 'kia', model: 'rio', year: 2010 },
]
// Создаем некоторую коллекцию и оборачиваем в класс Enumerable наш cars
const coll = new Enumerable(cars)
// Применяем различные методы, которые производят некоторые изменения с машинами
// Делаем сортировку
const result = coll.orderBy(car => car.year)
// Делаем фильтрацию
.where(car => car.brand === 'kia')
// Делаем выборку определенных полей изнутри
.select(car => car.model)
// Получаем новый массив, в котором видим, что выбирали и как
.toArray()
// ['rio', 'sorento' ]</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>Fluent Interface здесь заключается в том, что мы постоянно через точку продолжаем вызовы. В нашем примере идет вызов метода, а после этого точка, и это повторяется. Эти элементы кода могут вызываться бесконечно друг за другом, но бывают методы, которые являются окончательными.</p>
<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">toArray</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">where</code>. Поэтому методы бывают разные. Некоторые могут продолжать этот интерфейс, некоторые не могут.</p>
<p>В определении специалистов в области объектно-ориентированного программирования <strong>Fluent Interface</strong> — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.</p>
<p>Это не является прерогативой объектно-ориентированного программирования. Хотя из-за того, как вызываются методы, этот подход здесь выглядит именно так.</p>
<p>В функциональном программировании обычно используется чуть более мощная стратегия, которая называется <strong>функциональная композиция</strong>. В таком случае код остается неизменяемым, у нас будут чистые функции, и при этом она позволяет делать чуть более крутые вещи. Но это выходит за рамки этого урока. Главное понимать, что функциональная композиция и fluent interface имеют сходство.</p>
<p>Ниже приведен код на jQuery — это пример популярного DSL, который позволяет читабельно описывать то, что мы хотим сделать со страницей:</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">$(this).find('img').stop()
.animate({ opacity: 0.8 }, 300).end()
.find('.viewcasestudy').fadeIn('fast')</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>
<h2 id="heading-2-2">TDD</h2>
<p>Теперь поработаем по TDD. В данном случае библиотека простая, а код предсказуемый и детерминированный, поэтому все очень просто тестировать и писать:</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 cars = [
{ brand: 'bmw', model: 'm5', year: 2014 },
{ brand: 'bmw', model: 'm4', year: 2013 },
{ brand: 'kia', model: 'sorento', year: 2014 },
{ brand: 'kia', model: 'rio', year: 2010 },
{ brand: 'kia', model: 'sportage', year: 2012 },
]
// Делаем оборачивание коллекции машин
const coll = new Enumerable(cars)
// Делаем выборку
const result = coll
.where(car => car.brand === 'kia')
.where(car => car.year > 2011)
assert.deepEqual(result.toArray(), [cars[2], cars[4]])</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">assert</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">equal</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">deepEqual</code>. Этот метод тоже проверяет равенство. В следующих уроках мы поймем, зачем это нужно.</p>
<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">[cars[2], cars[4]]</code>. То есть машины под индексами 2 и 4 соответствуют параметрам выборки.</p>
<p>Это весь тест, который необходим, чтобы протестировать базовую функциональность нашей библиотеки. Осталось реализовать ее и посмотреть, как она устроена внутри.</p>
<h2 id="heading-2-3">Реализация</h2>
<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">// Определяем класс
class Enumerable {
// Конструктор, который устанавливает коллекцию внутрь
constructor(collection) {
this.collection = collection
}
where(fn) {
// Присваиваем новую коллекцию — полученную фильтрацию предыдущей
// По сути одну коллекцию заменяем на другую, пропуская через фильтр
this.collection = this.collection.filter(fn)
return this
}
toArray() {
return this.collection
}
}</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">where</code> мы производим действие, которое дает возможность использовать Fluent interface. Мы делаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">return this</code>.</p>
<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">this</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">this</code>. Это всё, что нужно для Fluent interface.</p>
<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">toArray()</code>, который возвращает саму коллекцию. Больше никаких преобразований здесь нет. Поэтому реализация Fluent interface — очень простая.</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">const result = coll.orderBy(car => car.year)
result.where(car => car.brand === 'kia')
result.select(car => car.model)
result.toArray()</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">orderBy</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">this</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">result</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">this</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">coll</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">coll</code>. И после этого мы продолжаем вызывать методы.</p>
<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">where</code> заменяет одну коллекцию на другую, он делает это в рамках одного объекта. В итоге получается, что мы мутируем объект каждый раз, когда вызываем такие методы.</p>
<h2 id="heading-2-4">Выводы</h2>
<p>В этом уроке мы познакомились с паттерном проектирования Fluent Interface. Мы узнали, что в определении специалистов в области объектно-ориентированного программирования <strong>Fluent Interface</strong> — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.</p>
<p>Также узнали, что в функциональном программировании обычно используется <strong>функциональная композиция</strong>, что имеет сходство с Fluent Interface.</p>
<p>Еще нам удалось протестировать базовую функциональность нашей библиотеки и реализовать ее. Также мы изучили, как она устроена внутри.</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/js-sicp?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">1 месяц</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">СИКП на JS</p><p 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="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png" alt="СИКП на 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">от 3 900 ₽</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-collections/lessons/fluent_interface/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 / 11</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-collections/lessons/fluent_interface/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>