В этом уроке мы познакомимся еще с одной особенностью, которая касается изменяемых данных. Это так называемые ссылки. И знакомиться мы с ними будем через Lazy Evaluation.
Lazy Evaluation
Lazy Evaluation — это ленивые вычисления или отложенные вычисления, которые применяются в некоторых языках программирования. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.
В реальности это касается не только языков. Это применяется и на уровне библиотек. Сейчас мы увидим, зачем это нужно, и как это работает.
Ленивость в том или ином виде существует во всех языках программирования. В основном это касается логических выражений. И в JavaScript она тоже есть.
Например, если мы встречаем такое логическое выражение, то его выполнение идет слева направо:
Если мы проверяем true, и далее стоит символ или (||), то нам неважно, что будет справа. Эта часть кода не повлияет на то, что результатом будет истина.
Это такая стратегия оптимизации внутри, которая позволяет не вычислять правое значение. Программисты пользуются этим, чтобы проверять существование какого-то объекта, например, что он не равен NULL, и вызывать дальше какой-то метод. Это проверка на существование позволяет не писать сложные куски кода.
Если используется false и символ «и» (&&), то неважно, что будет справа. В любом случае будет вычислено false:
Язык, в котором ленивые вычисления повсеместные, — это язык Haskell.
Ленивые коллекции
Ленивые вычисления помогают при работе с ленивыми коллекциями. Для практически любого языка написано большое количество подобных коллекций. Во многих языках они идут из коробки, например, в языке Ruby.
В Java есть дополнительные библиотеки. И в JavaScript их тоже достаточно много. Посмотрим на пример библиотеки из GitHub:
Эта библиотека показывает, как она работает. Здесь мы делаем Lazy.generate и передаем туда функцию, которая может сгенерировать числа. В данном случае это Math.random. После этого мы делаем map, который преобразует их все, делает какие-то умножения, округления и сложения. Далее находим uniq — уникальные числа. И после этого мы берем 300 элементов.
Тут возникает сразу несколько вопросов. Например, как могут применяться перечисленные функции, если мы не знаем, в каком количестве будут сгенерированы элементы посредством функции рандомизации. Так как речь идет о ленивых коллекциях, можно догадаться, что на самом деле никаких вычислений не происходит. Мы хоть и задаем их, но не пытаемся использовать.
Поэтому в реальности каждый такой вызов складывается внутрь — запоминается внутри этого объекта. Вычисления при этом не происходят.
Когда мы в самом конце вызываем функцию take и передаем туда параметр 300, становится понятно, что мы хотим использовать их. При этом нам нужно взять 300 элементов. В этот момент начинается отработка всех функций, которые были перечислены.
И если мы переберем их с помощью number.each((e) => console.log(e)), то получим результат.
Необязательно делать take. Часто конкретное использование — это использование уже циклических различных конструкций или функций высшего порядка, которые делают перебор. Например, это each.
Польза ленивых коллекций
Ленивые коллекции позволяют работать с бесконечными списками. При этом они не пытаются генерировать и забивать всю память.
Если бы ленивых коллекций не было, то строчка Lazy.generate(Math.random) просто бы повесила нам компьютер, так как бесконечно пыталась бы создать коллекцию. Но в нашем примере это не происходит.
Также ленивые коллекции позволяют строить гораздо более эффективную обработку. Например, в нашем примере мы описываем цепочку функций. Если у нас нет оптимизаций в языке, то обычно функции высшего порядка обрабатывают коллекции последовательно. То есть сначала функция делает один проход, потом другая функция делает еще один. В итоге получается много проходов.
Обычно коллекции достаточно небольшие. Поэтому в них не будет больших проблем при таком подходе. Также в некоторых языках есть оптимизации. И ленивые коллекции тоже позволяют это оптимизировать.
Так в каждом проходе применяются все эти функции для каждого элемента. И за счет этого вместо десятков проходов по коллекции мы можем получить только один.
LINQ
Разберем, как добавить ленивые функции в LINQ. Для этого надо сделать несколько изменений:
Теперь у нас внутри есть operations — операции, которые мы собираемся делать. И здесь используется еще один подход.
В третьей строчке кода написано operations или пустой массив. Это нужно в том случае, когда нам не передают operations. В обычном случае его никто не передает, не подразумевается, что пользователь знает про эти параметры и пользуется ими. Мы просто получаем на вход undefined и результатом проверки будет пустой массив в том случае, если operations не передан.
Работа в функции выбора происходит следующим образом. Сначала мы вызываем slice на operations.
Это важно, потому что если мы начнем менять operations сразу и добавлять туда новую операцию, то мы поменяем текущий объект. Это объект, из которого мы пытаемся получить новый. Новый должен обладать новым поведением и новым свойством выборки. Поэтому мы сначала генерируем новый operations и после этого уже туда добавляем функцию обработки. В нашем случае это будет map.
По сути у нас в operations хранятся функции, которые принимают на вход коллекцию и делают внутри обработку. В данном случае мы добавляем map.
В итоге этот код не выполняется, так как это отложенные вычисления — мы их просто запомнили. После этого мы передаем копию collection в new Enumerable первым параметром, а вторым параметром — newOps.
Здесь становится понятно, как должен выглядеть наш toArray, который должен применить к коллекции все операции. То есть нам нужно пропустить коллекцию сквозь все функции, которые там описаны, и получить результат. Но здесь есть еще одна ошибка, которая неочевидна.
Сравнение объектов
Попробуем сравнить объекты. Например, если сравнить два пустых массива, то можно выяснить, что они друг другу неравны. Если массив будет не пустой, произойдет то же самое:
И то же самое касается пустых и непустых объектов:
В этих примерах поведение одинаковое, потому что и то и то является объектом.
Проблема в том, что объекты хранятся по ссылке. То есть когда мы делаем такое присваивание, то внутри a оказывается не сам объект или значение, а ссылка на него. Поэтому когда мы их сравниваем — это создание нового объекта. И ссылка ведет на одну область памяти.
А в b происходит создание другого объекта, несмотря на то, что он структурно тот же. Но это не тот же объект. То есть здесь понятие изменяемости вводит понятие разности.
Например, счет с деньгами может быть один. При этом на нем сначала то одно количество денег, то другое. Но счет все равно один. Здесь та же ситуация.
Причем примитивные типы являются неизменяемыми, они присваиваются и работают не по ссылке, а по значению. Их всегда можно сравнить. А все типы объектов сравниваются по ссылкам, и поэтому они так работают.
Если мы работаем в тестах, то equal тоже нам скажет, что они false — неравны, а deepEqual скажет, что они true — равны:
Есть хороший пример, по которому понятно, как это работает, и как об этом думает JavaScript.
Например, для потребителя неважно, какая у нас купюра, важен только ее номинал. Допустим, у нас есть 100 долларов. Если мы поменяем купюру, у нас все равно окажется 100 долларов. Для нас важно само значение. А для производителя купюр, каждая купюра имеет свой номер, и ведется соответствующий учет.
Передача по ссылке
Разберемся, в чем у нас возникает коллизии, когда мы получаем поведение, которое нас чаще всего не устраивает. Пример:
Здесь в функцию, которая принимает коллекцию и делает ее сортировку, передаем numbers. В этом случае происходит так называемый pass-by-reference — передача по ссылке.
Если мы распечатаем numbers, мы увидим, что они отсортированы. Дело в том, что мы передаем туда на самом деле ссылку на изначальный массив. А sort сортирует in place — перемешивает всю коллекцию и в итоге numbers меняются. Это и есть сайд-эффект, когда наша функция нечистая.
Это иногда нужно для эффективности, но желательно не писать такой код, хотя с объектами так всегда и получается.
Общий пример
Рассмотрим общий пример, чтобы понять, как это работает. Допустим, у нас есть функция, которая принимает три параметра:
- a — это скаляр, число. В этом случае мы говорим, что a = a * 10.
- b — это объект, мы у него меняем одно поле
- c — это тоже объект, но мы его целиком заменяем на другой объект
Далее вызываем функцию, которую определили выше, передаем туда эти параметры и распечатываем. Посмотрим, что же из них изменилось:
Мы видим, что num не изменился. Как он был 10, так и остался. Он передается по значению, и когда мы говорим a = a * 10, создается локальное окружение, внутри которого записывается свой a. Он с внешним a никак не связан.
Далее мы вводим obj1.item, и он действительно изменился. Если объекты передаются по ссылке и мы меняем у него какое-то поле внутри, то меняется внешний объект, потому что это он и есть.
Третий вариант: мы распечатываем obj2.item, и он равен unchanged. Мы производили полную замену, при этом имя было тоже c. Но внутри теперь хранится ссылка на другой объект.
Это означает, что если у нас были другие ссылки на исходный объект, то они остаются. При этом сам объект остается неизмененный.
Выводы
В этом уроке мы познакомились еще с одной особенностью, которая касается изменяемых данных. Это так называемые ссылки. Мы узнали, что такое Lazy Evaluation. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.
Также мы узнали, что ленивые вычисления помогают при работе с ленивыми коллекциями. Для практически любого языка написано большое количество подобных коллекций. Они позволяют работать с бесконечными списками и строить гораздо более эффективную обработку.
Еще мы разобрали, как добавлять ленивые функции в LINQ и как сравнивать пустые и непустые объекты.
<!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:15:29 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="L8o9L2VPDTylSaUcy7Wzc5fyPdmdVQcQGMgTmIFHLmLAG_YYlzGgXBMKgYTHukMEV_sQc5Vi-bKlKInM00DJDA";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>Ленивые вычисления | JS: Коллекции</title>
<meta name="description" content="Ленивые вычисления / JS: Коллекции: Знакомимся с ленивыми вычислениями и изучаем эту особенность языка в контексте работы с коллекциями в JavaScript">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-collections/lessons/lazy/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Ленивые вычисления">
<meta property="og:title" content="JS: Коллекции">
<meta property="og:description" content="Ленивые вычисления / JS: Коллекции: Знакомимся с ленивыми вычислениями и изучаем эту особенность языка в контексте работы с коллекциями в JavaScript">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-collections/lessons/lazy/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="9yvADZEAt5G94HjhF1HfIRglKlmWTu6hiokFlXiGcq0Y-gs6Y34a8QujXHkbXi9W2CwH8555EAM3aZ_BKoGVww" />
<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:15:29.107Z","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":"F524DpbZPpN0Nse_sgvwOTIlns5jpCHn49W-livQcIP4THM5ZKeT88J14ye-BABO8iyzZGuT30VeNSTCedeX7Q","topics":[{"id":48802,"title":"Добрый день, объясните пожалуйста что я делаю не правильно\nМой код-ревью https://ru.hexlet.io/code_reviews/332355","plain_title":"Добрый день, объясните пожалуйста что я делаю не правильно Мой код-ревью https://ru.hexlet.io/code_reviews/332355 ","creator":{"public_name":"Татьяна Чернышова","id":309206,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":104778,"body":"**Татьяна**, приветствую.\n\nКак показывают тесты - возвращается объект вместо массива:\n\n expect(received).toEqual(expected) // deep equality\n\n Expected: [\"m5\", \"m4\", \"sorento\", \"rio\", \"sportage\"]\n Received: {\"collection\": [{\"brand\": \"bmw\", \"model\": \"m5\", \"year\": 2014}, ...\n\n 20 | it('select', () => {\n 21 | const result = coll.select((car) => car.model);\n > 22 | expect(result.toArray()).toEqual(['m5', 'm4', 'sorento', 'rio', 'sportage']);\n | ^\n\nСделайте чтобы функция `toArray` возвращала массив и с помощью отладочной печати и вывода тестов сможете устранить другие ошибки, если они есть.","topic_id":48802}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":20224,"title":"Здравствуйте! Второй день мучился над заданием, но по поводу toArray() не было даже конкретных мыслей даже почитав обсуждение.\nПроблему этой функции уже во многих местах тут обсуждали и я даже вчера изложил под одним топиком свою проблему:\n> Здравствуйте! Раз я дочитал обсуждения до этого места, то понятно, что решение мне пока не далось. Про пример select из теории сразу вспомнил и понял, что так нужно реализовывать остальные функции. НО, про toArray даже после после вашего здесь обсуждения, нет никаких мыслей.\nТолько понял, что нужно использовать reduce. Но для чего? как? и как должна работать toArray? что должна сделать toArray?\nПока даже никаких мыслей, к сожалению... Наверное, не понял, как должны запускаться отложенные вычисления. В теории про этот момент вроде только передача последней функции параметра (300). Если сегодня не решу, то ответы на мои вопросы понадобятся завтра еще)) Помогите, если можно. Спасибо!\n\nНо так ка никто не ответил и я сам ничего нового не придумал даже пройдя заново курс JS: Коллекции с самого начала до сего момента, я посмотрел решение учителя. И тут самое ужасное! Решение учителя еще больше поставило в тупик:( И в такой тупик, что я даже не знаю, как мне сейчас сформулировать вопросы, что не понятно! \n\n**Александр О.** просил писать мысли о том, что нужно на наш взгляд улучшить. Так вот.Такое чувство, что мы очень классно разбирали функции, столько тем на них было, упражнений разных, а тут в последних темах как \"по верхушкам\". Может я не прав, просто нужен опыт. Но, к примеру, узнав, что нужно использовать reduce(), сто раз загуглив его, я даже не понял к чему его применить. Как оказалось к this.operations, а не this.collection.\n\nТеперь попробую прокомментировать решение учителя и то, что не понятно в нем.\n1. Ну про constructor говорилось в теории, тут все ок.\n2. build(fn) добавили просто, чтобы не переписывать одинаковый код, я так понял. И реализация добавления отложенной функции при помощи concat - это изощрение учителя.\n3. orderBy и where работают по тому же принципу, что и select, что подробно рассмотрено в теории.\n4. после всего мы должны применить toArray() по после чего должны выполниться отложенные вычисления и на выходе должен быть обычный массив.\n5. В последней строчке сложно представить, что происходит, но попробуем:\nк массиву операций применяется reduce, а в качестве аккумулятора исходная коллекция. Обходя массив с операциями мы на каждой итерации получаем \"отложенную нами функцию\" и к ней применяем функцию (жесть!) с двумя аргументами, которая возвратит нам результат применения \"отложенной функции\" к нашему аккумулятору. И так далее.\nНу вот, как обычно при написании проблемы вроде все сошлось! Но все ровно все плохо, так как никогда бы сам не додумался ((.\nНе знаю, можно ли это было решить самому, пройдя только курс до этой темы...\n","plain_title":"Здравствуйте! Второй день мучился над заданием, но по поводу toArray() не было даже конкретных мыслей даже почитав обсуждение. Проблему этой функции уже во многих местах тут обсуждали и я даже вчера изложил под одним топиком свою проблему: Здравствуйте! Раз я дочитал обсуждения до этого места, то понятно, что решение мне пока не далось. Про пример select из теории сразу вспомнил и понял, что так нужно реализовывать остальные функции. НО, про toArray даже после после вашего здесь обсуждения, нет никаких мыслей. Только понял, что нужно использовать reduce. Но для чего? как? и как должна работать toArray? что должна сделать toArray? Пока даже никаких мыслей, к сожалению... Наверное, не понял, как должны запускаться отложенные вычисления. В теории про этот момент вроде только передача последней функции параметра (300). Если сегодня не решу, то ответы на мои вопросы понадобятся завтра еще)) Помогите, если можно. Спасибо! Но так ка никто не ответил и я сам ничего нового не придумал даже пройдя заново курс JS: Коллекции с самого начала до сего момента, я посмотрел решение учителя. И тут самое ужасное! Решение учителя еще больше поставило в тупик:( И в такой тупик, что я даже не знаю, как мне сейчас сформулировать вопросы, что не понятно! Александр О. просил писать мысли о том, что нужно на наш взгляд улучшить. Так вот.Такое чувство, что мы очень классно разбирали функции, столько тем на них было, упражнений разных, а тут в последних темах как \"по верхушкам\". Может я не прав, просто нужен опыт. Но, к примеру, узнав, что нужно использовать reduce(), сто раз загуглив его, я даже не понял к чему его применить. Как оказалось к this.operations, а не this.collection. Теперь попробую прокомментировать решение учителя и то, что не понятно в нем. 1. Ну про constructor говорилось в теории, тут все ок. 2. build(fn) добавили просто, чтобы не переписывать одинаковый код, я так понял. И реализация добавления отложенной функции при помощи concat - это изощрение учителя. 3. orderBy и where работают по тому же принципу, что и select, что подробно рассмотрено в теории. 4. после всего мы должны применить toArray() по после чего должны выполниться отложенные вычисления и на выходе должен быть обычный массив. 5. В последней строчке сложно представить, что происходит, но попробуем: к массиву операций применяется reduce, а в качестве аккумулятора исходная коллекция. Обходя массив с операциями мы на каждой итерации получаем \"отложенную нами функцию\" и к ней применяем функцию (жесть!) с двумя аргументами, которая возвратит нам результат применения \"отложенной функции\" к нашему аккумулятору. И так далее. Ну вот, как обычно при написании проблемы вроде все сошлось! Но все ровно все плохо, так как никогда бы сам не додумался ((. Не знаю, можно ли это было решить самому, пройдя только курс до этой темы... ","creator":{"public_name":"Денис Тимошенко","id":186656,"is_tutor":false},"comments":[{"creator":{"public_name":"Денис Тимошенко","id":186656,"is_tutor":false},"id":42840,"body":"Спасибо, Александр, за такой развернутый комментарий. Для себя просто нужно определить в каких ситуациях будет разумно применить уже известные нам функции.","topic_id":20224},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":42806,"body":"Добрый день!\n\n> Проблему этой функции уже во многих местах тут обсуждали и я даже вчера изложил под одним топиком свою проблему\n\nДа, лучше создавать отдельный топик со своим вопросом, чтобы он не был случайно пропущен.\n\n> build(fn) добавили просто, чтобы не переписывать одинаковый код, я так понял.\n\nДа, устранение дублирования кода.\n\n> И реализация добавления отложенной функции при помощи concat - это изощрение учителя.\n\nНе совсем так, слишком художественно про \"изощрение\". Эта функция позволяет добавить элемент, не изменяя текущий массив. Кроме того, она позволяет добавить сразу несколько элементов или даже массив - но это преимущество раскроется в следующих практиках.\n\n> после всего мы должны применить toArray() по после чего должны выполниться отложенные вычисления и на выходе должен быть обычный массив.\n\nДа.\n\n> массиву операций применяется reduce, а в качестве аккумулятора исходная коллекция.\n\nДа. `reduce` - потому что это НЕ **отображение** и НЕ **фильтрация**, в результате применения нескольких разных операций к коллекции надо получить совершенно новое значение (т.е. редуцировать коллекцию `collection` к новому значению).\n\n`reduce` вызываем на массиве операций, потому что делаем перебор операций и каждую операцию выполняем над всей коллекцией. То есть в ходе редуцирования нам надо организовать обход массива операций.\n\n`collection` используем в качестве аккумулятора, потому что это значение будем меняться, мы его используем как \"глину\", применяя на каждом шаге к ней очередную операцию из `operations`. Т.е. здесь будет накапливаться итоговый результат.\n\nЛирическое отступление. Помните в финале сказки про конька-горбунка:\n\n```\nВот, коль хочешь ты жениться\nИ красавцем учиниться —\nТы, без платья, налегке,\nИскупайся в молоке;\nТут побудь в воде варёной,\nА потом ещё в студёной.\n```\n\nТак вот, искупать в молоке/кипятке/студёной воде - это массив операций, operations. А главного героя, можно рассматривать как collections. На кажом этапе он подвергается обработке операцией и в итоге редуцируется к новому состоянию - \"становится красавцем\". Образная аналогия, так сказать :)\n\n> Обходя массив с операциями мы на каждой итерации получаем \"отложенную нами функцию\"\n\nДа, ведь мы обходим массив с этими функциями и на каждой операции получаем очередную функцию - отдельную операцию.\n\n> и к ней применяем функцию (жесть!) с двумя аргументами, которая возвратит нам результат применения \"отложенной функции\" к нашему аккумулятору.\n\nЗдесь не совсем ясно вы описываете. Видимо, не до конца хорошо понимаете механизм работы reduce. На каждой итерации reduce вызывает функцию с двумя аргументами - элементом массива (это отдельная опреация) и аккумулятором (результатом от прошлой итерации). Всё это сводится к тому, что на каждой итерации мы последовательно обрабатываем коллекцию разными операциями (сначала искупали героя в молоке, потом в кипятке, потом в студёной воде...).\n\n> Ну вот, как обычно при написании проблемы вроде все сошлось!\n\nВот, правду говорят, что [метод утёнка](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D1%83%D1%82%D1%91%D0%BD%D0%BA%D0%B0) помогает! :)\n\n> Но все ровно все плохо, так как никогда бы сам не додумался ((. Не знаю, можно ли это было решить самому, пройдя только курс до этой темы...\n\nНе паникуйте, бывают сложные задачи, над которыми надо посидеть и подумать. Не всё становится ясным сразу, часть проясняется позже - это нормально. Разные темы для разных учеников даются по разному. Главное, чтобы процесс обучения был систематическим и не стоит опускать руки.","topic_id":20224},{"creator":{"public_name":"Максим Шипулин","id":204082,"is_tutor":false},"id":53759,"body":"Как работает я понял, а как должен был я догадаться как это использовать - не понял. Совсем невозможно .","topic_id":20224},{"creator":{"public_name":"Александр Кириллов","id":190404,"is_tutor":false},"id":45591,"body":"Честно сказать я бы тоже скорее всего не догадался насчет редьюс. Это было сложно настолько что даже худо-будно решив, не уверен что до конца понял механизм всего этого(","topic_id":20224},{"creator":{"public_name":"Алексей Королёв","id":188357,"is_tutor":false},"id":58195,"body":"> Такое чувство, что мы очень классно разбирали функции, столько тем на них было, упражнений разных, а тут в последних темах как \"по верхушкам\".\n\nКак \"по верхушкам\" – такое же ощущение, не закрепляется материал, надеюсь испытания помогут в этом деле","topic_id":20224}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":27408,"title":"Тему вообще не понял. Где что почитать, чтобы разобраться? Вообще критично ли ее понимание для дальнейшего обучения? ","plain_title":"Тему вообще не понял. Где что почитать, чтобы разобраться? Вообще критично ли ее понимание для дальнейшего обучения? ","creator":{"public_name":"Roman Serikov","id":142522,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Serikov","id":142522,"is_tutor":false},"id":58774,"body":"Где-то я на дня читал или бабули у подъезда рассказали, что ленивые вычисления влекут задержки в доступе к объектам - результатам таких вычислений. Это правда? Ну, что касается задержек, я имею ввиду. ","topic_id":27408},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":58772,"body":"> я вообще ничего не понял, как это работает )))\n\nНо с чего-то конкретного всё же начать стоит, хотя бы с того самого первого место, где на вас нападает чувство непонимания ;)","topic_id":27408},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":58832,"body":"> Где-то я на дня читал или бабули у подъезда рассказали\n\nРоман, мало информации, неконкретный вопрос, не представляется возможным прокомментировать выдернутое из контекста утверждение. Но если вы действительно \"вообще не поняли\" тему урока, то не стоит начинать её разбор со стороны неких оптимизаций и производительности, далеко не уйдёте.","topic_id":27408},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":58757,"body":"Добрый день!\n\n> Тему вообще не понял.\n\nРоман, тогда предлагаю попробовать разобраться в этой теме. Но, чтобы начать распутывать \"клубок\" непонимания, вам надо это \"вообще не понял\" конкретизировать, чтобы можно было от чего-то отталкиваться. Сформулируйте вопросы, уточните проблему.","topic_id":27408},{"creator":{"public_name":"Roman Serikov","id":142522,"is_tutor":false},"id":58771,"body":"Я распечатал решение учителя на бумажках в нескольких экземплярах. Как только найду время, буду в нем разбираться. В том то и дело, что чего-то конкретного нет, потому что я вообще ничего не понял, как это работает )))\n","topic_id":27408}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":32108,"title":"В решении учителя, если в operations не передаются действия, то результат будет пустым списком: empty-operations.reduce(something) == [].\n\nЯ думаю, что правильнее возвращать хотя бы identity таким образом: if (operations.length === 0) { operations.push(items => items) }","plain_title":"В решении учителя, если в operations не передаются действия, то результат будет пустым списком: [empty-operations].reduce(something) == []. Я думаю, что правильнее возвращать хотя бы identity таким образом: if (operations.length === 0) { operations.push(items => items) } ","creator":{"public_name":"Rustam A.","id":177980,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":69803,"body":"**Rustam A.**, добрый день.\n\nВ данной ситуации это будет лишняя операция, так как по сути ее выполняет функция reduce, возвращая значение, которым она инициализирована.","topic_id":32108}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":27478,"title":"Чет какая-то абра-кадабра.","plain_title":"Чет какая-то абра-кадабра. ","creator":{"public_name":"Магжан Сыдыков","id":181574,"is_tutor":false},"comments":[{"creator":{"public_name":"Магжан Сыдыков","id":181574,"is_tutor":false},"id":58959,"body":"Проблема скорее общая. В какой-то момент я потерял нить логики. Буду перекапывать. ","topic_id":27478},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":58950,"body":"Магжан, можете подробнее описать, какая у вас проблема?","topic_id":27478},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":58964,"body":"Задавайте вопросы, если нужна помощью в поиске этой самой нити :)","topic_id":27478}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":19724,"title":"Подскажите, как toArray() узнает, какие функции надо применить к коллекции? Мы сохраняли их в newOps в каждом методе отдельно, но toArray() ничего об этом не знает.\n\nНужно вручную передать список методов или это можно сделать как-то разом для всех методов коллекции?\n","plain_title":"Подскажите, как toArray() узнает, какие функции надо применить к коллекции? Мы сохраняли их в newOps в каждом методе отдельно, но toArray() ничего об этом не знает. Нужно вручную передать список методов или это можно сделать как-то разом для всех методов коллекции? ","creator":{"public_name":"Сергей Кулаков","id":181653,"is_tutor":false},"comments":[{"creator":{"public_name":"Илья Токарев","id":76797,"is_tutor":false},"id":41652,"body":"toArray использует operations, посмотрите теорию повнимательней.","topic_id":19724},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":41685,"body":"Все нужные операции уже сохранены в объекте. Нужно их использовать.","topic_id":19724}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":9588,"title":"не могу зайти на задание, код ошибки 500","plain_title":"не могу зайти на задание, код ошибки 500 ","creator":{"public_name":"Дмитрий Рожко","id":131090,"is_tutor":false},"comments":[{"creator":{"public_name":"Дмитрий Рожко","id":131090,"is_tutor":false},"id":19620,"body":"теперь переходит, но контейнер не грузится, даже после нажатия на сброс результата\n","topic_id":9588},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":19622,"body":"Извиняюсь, были неполадки на серверах. Сейчас все должно быть хорошо.","topic_id":9588}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":12942,"title":"Мне кажется, или в тестах не проверяется `orderBy`?","plain_title":"Мне кажется, или в тестах не проверяется orderBy? ","creator":{"public_name":"Ignat","id":162470,"is_tutor":false},"comments":[{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":27259,"body":"Конечно же. Добавил проверочку, спасибо вам за внимательность :)","topic_id":12942},{"creator":{"public_name":"Ignat","id":162470,"is_tutor":false},"id":27125,"body":"Тут проверяется, что `orderBy` иммутабелна, но не проверяется, как она сортирует. Такая реализация проходит проверку:\n```\norderBy(fn, direction) {\n return this;\n}\n```\n","topic_id":12942},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":27115,"body":"```\nit('should be immutable', () => {\n coll.orderBy(car => car.year, 'asc').toArray();\n const result = coll.where(car => car.brand === 'kia')\n .where(car => car.year > 2011).select(car => car.model);\n\n expect(result.toArray()).toEqual(['sorento', 'sportage']);\n });\n```","topic_id":12942}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":27800,"title":"Здравствуйте! Не могу понять почему не работает код. Проходит только первый тест.\n\n`// removed`","plain_title":"Здравствуйте! Не могу понять почему не работает код. Проходит только первый тест. // removed ","creator":{"public_name":"Анастасия Сухарева","id":184536,"is_tutor":false},"comments":[{"creator":{"public_name":"Анастасия Сухарева","id":184536,"is_tutor":false},"id":59788,"body":"Спасибо большое, такой глупой ошибки я никак не ожидала! :D Вывод: всегда проверяйте названия свойств объектов и не забывайте буковки! Сидела три дня, решила, что программирование - это не моё :D","topic_id":27800},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":59774,"body":"Если вариант с undefined в конструктора отпадает, то переходит к следующему варианту: вызов несуществующего свойства. Проверяйте имена.","topic_id":27800},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":59768,"body":"Поправка: конструктор вашего класса, который задаёт свойства в this.","topic_id":27800},{"creator":{"public_name":"Анастасия Сухарева","id":184536,"is_tutor":false},"id":59765,"body":"Когда свойство объекта создано, но в него ничего не передано. Но в конструкторе ему присваивается коллекция","topic_id":27800},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":59773,"body":"В сообщении указано, что ошибка в orderBy, вы там исправили и не сработало?","topic_id":27800},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":59766,"body":"`Undefined`, можно перевести как неопределённый. То есть, у нас два варианта, либо свойство с таким именем не задано, либо такое значение передано в конструктора.","topic_id":27800},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":59791,"body":"В js это довольно распространенная ошибка и среди работающих разработчиков, и не стоит себя за это корить, когда еще только учитесь. Успехов.","topic_id":27800},{"creator":{"public_name":"Анастасия Сухарева","id":184536,"is_tutor":false},"id":59772,"body":"Как я поняла, когда я возвращаю new Enumerable(this.collection.slice(), newOps) в конструктор не передаётся ссылка на сам объект, поэтому в ошибке undefined.\nНо передать её у меня не получается. Пыталась сделать так:\n\n`// removed`","topic_id":27800},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":59732,"body":"Здравствуейте. Если прочесть сообщение и обратить внимание на указатель, то у вас `this.collecton` принимает значение `undefined`. В каких случаях свойство у объекта (данном случае `this`) в js принимает такое значение?","topic_id":27800},{"creator":{"public_name":"Анастасия Сухарева","id":184536,"is_tutor":false},"id":59767,"body":"Я не могу понять, как в конструктор передаётся undefined, мы ведь передаём туда копию свойства объекта this.collecton.slice()","topic_id":27800}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}},{"id":6185,"title":"подскажите почему теперь в функции reduce сначала идет в аргументе acc а потом element, в отличие от нашей реализации где они шли в обратном порядке? Это сокращенная версия от (acc, element, index, arr) где два последних значения undefind за ненадобностью?","plain_title":"подскажите почему теперь в функции reduce сначала идет в аргументе acc а потом element, в отличие от нашей реализации где они шли в обратном порядке? Это сокращенная версия от (acc, element, index, arr) где два последних значения undefind за ненадобностью? ","creator":{"public_name":"Артем Шустов","id":123406,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":11268,"body":"Этот `reduce` является частью js и у него такой порядок аргументов.","topic_id":6185}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Ленивые вычисления","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":480,"slug":"js_collections_references_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Реализуйте ленивую версию `Enumerable`.\nИнтерфейс включает в себя следующие методы: `select()`, `where()`, `orderBy()`, `toArray()`.\n\n### Подсказки\n\n* Так как коллекция ленивая, не нужно выполнять вычислений до вызова `toArray()`, вместо\n этого необходимо формировать коллекцию из отложенных вычислений.\n","prepared_readme":"## Enumerable.js\n\nРеализуйте ленивую версию `Enumerable`.\nИнтерфейс включает в себя следующие методы: `select()`, `where()`, `orderBy()`, `toArray()`.\n\n### Подсказки\n\n* Так как коллекция ленивая, не нужно выполнять вычислений до вызова `toArray()`, вместо\n этого необходимо формировать коллекцию из отложенных вычислений.\n","has_solution":true,"entity_name":"Ленивые вычисления"},"units":[{"id":1419,"name":"theory","url":"/courses/js-collections/lessons/lazy/theory_unit"},{"id":1421,"name":"quiz","url":"/courses/js-collections/lessons/lazy/quiz_unit"},{"id":1420,"name":"exercise","url":"/courses/js-collections/lessons/lazy/exercise_unit"}],"links":[{"id":423144,"name":"TDD","url":"https://ru.hexlet.io/courses/js-testing/lessons/tdd/theory_unit"},{"id":423145,"name":"LINQ","url":"https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/"},{"id":423146,"name":"DSL","url":"https://en.wikipedia.org/wiki/Domain-specific_language"}],"ordered_units":[{"id":1419,"name":"theory","url":"/courses/js-collections/lessons/lazy/theory_unit"},{"id":1421,"name":"quiz","url":"/courses/js-collections/lessons/lazy/quiz_unit"},{"id":1420,"name":"exercise","url":"/courses/js-collections/lessons/lazy/exercise_unit"}],"id":713,"slug":"lazy","state":"approved","name":"Ленивые вычисления","course_order":700,"goal":"Знакомимся с ленивыми вычислениями и изучаем эту особенность языка в контексте работы с коллекциями в JavaScript","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"246296829","theory":"В этом уроке мы познакомимся еще с одной особенностью, которая касается изменяемых данных. Это так называемые ссылки. И знакомиться мы с ними будем через Lazy Evaluation.\n\n## Lazy Evaluation\n\n**Lazy Evaluation** — это ленивые вычисления или отложенные вычисления, которые применяются в некоторых языках программирования. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.\n\nВ реальности это касается не только языков. Это применяется и на уровне библиотек. Сейчас мы увидим, зачем это нужно, и как это работает.\n\nЛенивость в том или ином виде существует во всех языках программирования. В основном это касается логических выражений. И в JavaScript она тоже есть.\n\nНапример, если мы встречаем такое логическое выражение, то его выполнение идет слева направо:\n\n```javascript\n// true\ntrue || console.log('message')\n```\n\nЕсли мы проверяем `true`, и далее стоит символ или (`||`), то нам неважно, что будет справа. Эта часть кода не повлияет на то, что результатом будет истина.\n\nЭто такая стратегия оптимизации внутри, которая позволяет не вычислять правое значение. Программисты пользуются этим, чтобы проверять существование какого-то объекта, например, что он не равен `NULL`, и вызывать дальше какой-то метод. Это проверка на существование позволяет не писать сложные куски кода.\n\nЕсли используется `false` и символ «и» (`&&`), то неважно, что будет справа. В любом случае будет вычислено `false`:\n\n```javascript\n// false\nfalse && console.log('message')\n```\n\nЯзык, в котором ленивые вычисления повсеместные, — это язык Haskell.\n\n## Ленивые коллекции\n\nЛенивые вычисления помогают при работе с ленивыми коллекциями. Для практически любого языка написано большое количество подобных коллекций. Во многих языках они идут из коробки, например, в языке Ruby.\n\nВ Java есть дополнительные библиотеки. И в JavaScript их тоже достаточно много. Посмотрим на пример библиотеки из GitHub:\n\n```javascript\nconst numbers = Lazy.generate(Math.random)\n .map(e => Math.floor(e * 1000) + 1)\n .uniq()\n .take(300)\n\nnumbers.each(e => console.log(e))\n```\n\nЭта библиотека показывает, как она работает. Здесь мы делаем `Lazy.generate` и передаем туда функцию, которая может сгенерировать числа. В данном случае это `Math.random`. После этого мы делаем `map`, который преобразует их все, делает какие-то умножения, округления и сложения. Далее находим `uniq` — уникальные числа. И после этого мы берем 300 элементов.\n\nТут возникает сразу несколько вопросов. Например, как могут применяться перечисленные функции, если мы не знаем, в каком количестве будут сгенерированы элементы посредством функции рандомизации. Так как речь идет о ленивых коллекциях, можно догадаться, что на самом деле никаких вычислений не происходит. Мы хоть и задаем их, но не пытаемся использовать.\n\nПоэтому в реальности каждый такой вызов складывается внутрь — запоминается внутри этого объекта. Вычисления при этом не происходят.\n\nКогда мы в самом конце вызываем функцию `take` и передаем туда параметр `300,` становится понятно, что мы хотим использовать их. При этом нам нужно взять 300 элементов. В этот момент начинается отработка всех функций, которые были перечислены.\n\nИ если мы переберем их с помощью `number.each((e) => console.log(e))`, то получим результат.\n\nНеобязательно делать `take`. Часто конкретное использование — это использование уже циклических различных конструкций или функций высшего порядка, которые делают перебор. Например, это `each`.\n\n## Польза ленивых коллекций\n\nЛенивые коллекции позволяют работать с бесконечными списками. При этом они не пытаются генерировать и забивать всю память.\n\nЕсли бы ленивых коллекций не было, то строчка `Lazy.generate(Math.random)` просто бы повесила нам компьютер, так как бесконечно пыталась бы создать коллекцию. Но в нашем примере это не происходит.\n\nТакже ленивые коллекции позволяют строить гораздо более эффективную обработку. Например, в нашем примере мы описываем цепочку функций. Если у нас нет оптимизаций в языке, то обычно функции высшего порядка обрабатывают коллекции последовательно. То есть сначала функция делает один проход, потом другая функция делает еще один. В итоге получается много проходов.\n\nОбычно коллекции достаточно небольшие. Поэтому в них не будет больших проблем при таком подходе. Также в некоторых языках есть оптимизации. И ленивые коллекции тоже позволяют это оптимизировать.\n\nТак в каждом проходе применяются все эти функции для каждого элемента. И за счет этого вместо десятков проходов по коллекции мы можем получить только один.\n\n## LINQ\n\nРазберем, как добавить ленивые функции в LINQ. Для этого надо сделать несколько изменений:\n\n```javascript\nconstructor(collection, operations) {\n this.collection = collection;\n this.operations = operations || [];\n}\n\nselect (fn) {\n const newOps = this.operations.slice();\n newOps.push((coll) => coll.map(fn));\n return new Enumerable(this.collection.slice(), newOps);\n}\n```\n\nТеперь у нас внутри есть `operations` — операции, которые мы собираемся делать. И здесь используется еще один подход.\n\nВ третьей строчке кода написано `operations` или пустой массив. Это нужно в том случае, когда нам не передают `operations`. В обычном случае его никто не передает, не подразумевается, что пользователь знает про эти параметры и пользуется ими. Мы просто получаем на вход `undefined` и результатом проверки будет пустой массив в том случае, если `operations` не передан.\n\nРабота в функции выбора происходит следующим образом. Сначала мы вызываем `slice` на `operations`.\n\nЭто важно, потому что если мы начнем менять `operations` сразу и добавлять туда новую операцию, то мы поменяем текущий объект. Это объект, из которого мы пытаемся получить новый. Новый должен обладать новым поведением и новым свойством выборки. Поэтому мы сначала генерируем новый `operations` и после этого уже туда добавляем функцию обработки. В нашем случае это будет `map`.\n\nПо сути у нас в `operations` хранятся функции, которые принимают на вход коллекцию и делают внутри обработку. В данном случае мы добавляем `map`.\n\nВ итоге этот код не выполняется, так как это отложенные вычисления — мы их просто запомнили. После этого мы передаем копию `collection` в `new Enumerable` первым параметром, а вторым параметром — `newOps`.\n\nЗдесь становится понятно, как должен выглядеть наш `toArray`, который должен применить к коллекции все операции. То есть нам нужно пропустить коллекцию сквозь все функции, которые там описаны, и получить результат. Но здесь есть еще одна ошибка, которая неочевидна.\n\n## Сравнение объектов\n\nПопробуем сравнить объекты. Например, если сравнить два пустых массива, то можно выяснить, что они друг другу неравны. Если массив будет не пустой, произойдет то же самое:\n\n```javascript\n[] === []; // false\n['cat', 'dog'] === ['cat', 'dog'] // false\n```\n\nИ то же самое касается пустых и непустых объектов:\n\n```javascript\nconst a = {}\nconst b = {}\nа === b // false\n```\n\nВ этих примерах поведение одинаковое, потому что и то и то является объектом.\n\nПроблема в том, что объекты хранятся по ссылке. То есть когда мы делаем такое присваивание, то внутри `a` оказывается не сам объект или значение, а ссылка на него. Поэтому когда мы их сравниваем — это создание нового объекта. И ссылка ведет на одну область памяти.\n\nА в `b` происходит создание другого объекта, несмотря на то, что он структурно тот же. Но это не тот же объект. То есть здесь понятие изменяемости вводит понятие разности.\n\nНапример, счет с деньгами может быть один. При этом на нем сначала то одно количество денег, то другое. Но счет все равно один. Здесь та же ситуация.\n\nПричем примитивные типы являются неизменяемыми, они присваиваются и работают не по ссылке, а по значению. Их всегда можно сравнить. А все типы объектов сравниваются по ссылкам, и поэтому они так работают.\n\nЕсли мы работаем в тестах, то `equal` тоже нам скажет, что они `false` — неравны, а `deepEqual` скажет, что они `true` — равны:\n\n```javascript\nassert.equal(a, b) // false\nassert.deepEqual(a, b) // true\n```\n\nЕсть хороший пример, по которому понятно, как это работает, и как об этом думает JavaScript.\n\nНапример, для потребителя неважно, какая у нас купюра, важен только ее номинал. Допустим, у нас есть 100 долларов. Если мы поменяем купюру, у нас все равно окажется 100 долларов. Для нас важно само значение. А для производителя купюр, каждая купюра имеет свой номер, и ведется соответствующий учет.\n\n## Передача по ссылке\n\nРазберемся, в чем у нас возникает коллизии, когда мы получаем поведение, которое нас чаще всего не устраивает. Пример:\n\n```javascript\nconst numbers = [10, 8, 1, 7, 4]\n\nconst mySort = coll => coll.sort()\nmySort(numbers) // [ 1, 10, 4, 7, 8 ]\n\n// side-effect\nconsole.log(numbers) // [ 1, 10, 4, 7, 8]\n```\n\nЗдесь в функцию, которая принимает коллекцию и делает ее сортировку, передаем `numbers`. В этом случае происходит так называемый pass-by-reference — передача по ссылке.\n\nЕсли мы распечатаем `numbers`, мы увидим, что они отсортированы. Дело в том, что мы передаем туда на самом деле ссылку на изначальный массив. А `sort` сортирует in place — перемешивает всю коллекцию и в итоге `numbers` меняются. Это и есть сайд-эффект, когда наша функция нечистая.\n\nЭто иногда нужно для эффективности, но желательно не писать такой код, хотя с объектами так всегда и получается.\n\n## Общий пример\n\nРассмотрим общий пример, чтобы понять, как это работает. Допустим, у нас есть функция, которая принимает три параметра:\n\n```javascript\nconst f = (a, b, c) => {\n a = a * 10\n b.item = 'changed'\n с = { item: 'changed' }\n}\n\nconst num = 10\nconst obj1 = { item: 'unchanged' }\nconst obj2 = { item: 'unchanged' }\n\nf(num, obj1, obj2)\n\nconsole.log(num) // 10\nconsole.log(obj1.item) // changed\nconsole.log(obj2.item) // unchanged\n```\n\n* `a` — это скаляр, число. В этом случае мы говорим, что `a = a * 10`.\n* `b` — это объект, мы у него меняем одно поле\n* `c` — это тоже объект, но мы его целиком заменяем на другой объект\n\nДалее вызываем функцию, которую определили выше, передаем туда эти параметры и распечатываем. Посмотрим, что же из них изменилось:\n\n```javascript\nconsole.log(num) // 10\nconsole.log(obj1.item) // changed\nconsole.log(obj2.item) // unchanged\n```\n\nМы видим, что `num` не изменился. Как он был `10`, так и остался. Он передается по значению, и когда мы говорим `a = a * 10`, создается локальное окружение, внутри которого записывается свой `a`. Он с внешним `a` никак не связан.\n\nДалее мы вводим `obj1.item`, и он действительно изменился. Если объекты передаются по ссылке и мы меняем у него какое-то поле внутри, то меняется внешний объект, потому что это он и есть.\n\nТретий вариант: мы распечатываем `obj2.item`, и он равен `unchanged.` Мы производили полную замену, при этом имя было тоже `c`. Но внутри теперь хранится ссылка на другой объект.\n\nЭто означает, что если у нас были другие ссылки на исходный объект, то они остаются. При этом сам объект остается неизмененный.\n\n## Выводы\n\nВ этом уроке мы познакомились еще с одной особенностью, которая касается изменяемых данных. Это так называемые ссылки. Мы узнали, что такое Lazy Evaluation. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.\n\nТакже мы узнали, что ленивые вычисления помогают при работе с ленивыми коллекциями. Для практически любого языка написано большое количество подобных коллекций. Они позволяют работать с бесконечными списками и строить гораздо более эффективную обработку.\n\nЕще мы разобрали, как добавлять ленивые функции в LINQ и как сравнивать пустые и непустые объекты.\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/lazy/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">Теория: Ленивые вычисления</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Ленивые вычисления","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>В этом уроке мы познакомимся еще с одной особенностью, которая касается изменяемых данных. Это так называемые ссылки. И знакомиться мы с ними будем через Lazy Evaluation.</p>
<h2 id="heading-2-1">Lazy Evaluation</h2>
<p><strong>Lazy Evaluation</strong> — это ленивые вычисления или отложенные вычисления, которые применяются в некоторых языках программирования. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.</p>
<p>В реальности это касается не только языков. Это применяется и на уровне библиотек. Сейчас мы увидим, зачем это нужно, и как это работает.</p>
<p>Ленивость в том или ином виде существует во всех языках программирования. В основном это касается логических выражений. И в JavaScript она тоже есть.</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">// true
true || console.log('message')</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">true</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">||</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">NULL</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">false</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">&&</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">false</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// false
false && console.log('message')</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>Язык, в котором ленивые вычисления повсеместные, — это язык Haskell.</p>
<h2 id="heading-2-2">Ленивые коллекции</h2>
<p>Ленивые вычисления помогают при работе с ленивыми коллекциями. Для практически любого языка написано большое количество подобных коллекций. Во многих языках они идут из коробки, например, в языке Ruby.</p>
<p>В Java есть дополнительные библиотеки. И в JavaScript их тоже достаточно много. Посмотрим на пример библиотеки из GitHub:</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 numbers = Lazy.generate(Math.random)
.map(e => Math.floor(e * 1000) + 1)
.uniq()
.take(300)
numbers.each(e => console.log(e))</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">Lazy.generate</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">Math.random</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">map</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">uniq</code> — уникальные числа. И после этого мы берем 300 элементов.</p>
<p>Тут возникает сразу несколько вопросов. Например, как могут применяться перечисленные функции, если мы не знаем, в каком количестве будут сгенерированы элементы посредством функции рандомизации. Так как речь идет о ленивых коллекциях, можно догадаться, что на самом деле никаких вычислений не происходит. Мы хоть и задаем их, но не пытаемся использовать.</p>
<p>Поэтому в реальности каждый такой вызов складывается внутрь — запоминается внутри этого объекта. Вычисления при этом не происходят.</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">take</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">300,</code> становится понятно, что мы хотим использовать их. При этом нам нужно взять 300 элементов. В этот момент начинается отработка всех функций, которые были перечислены.</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">number.each((e) => console.log(e))</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">take</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">each</code>.</p>
<h2 id="heading-2-3">Польза ленивых коллекций</h2>
<p>Ленивые коллекции позволяют работать с бесконечными списками. При этом они не пытаются генерировать и забивать всю память.</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">Lazy.generate(Math.random)</code> просто бы повесила нам компьютер, так как бесконечно пыталась бы создать коллекцию. Но в нашем примере это не происходит.</p>
<p>Также ленивые коллекции позволяют строить гораздо более эффективную обработку. Например, в нашем примере мы описываем цепочку функций. Если у нас нет оптимизаций в языке, то обычно функции высшего порядка обрабатывают коллекции последовательно. То есть сначала функция делает один проход, потом другая функция делает еще один. В итоге получается много проходов.</p>
<p>Обычно коллекции достаточно небольшие. Поэтому в них не будет больших проблем при таком подходе. Также в некоторых языках есть оптимизации. И ленивые коллекции тоже позволяют это оптимизировать.</p>
<p>Так в каждом проходе применяются все эти функции для каждого элемента. И за счет этого вместо десятков проходов по коллекции мы можем получить только один.</p>
<h2 id="heading-2-4">LINQ</h2>
<p>Разберем, как добавить ленивые функции в LINQ. Для этого надо сделать несколько изменений:</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">constructor(collection, operations) {
this.collection = collection;
this.operations = operations || [];
}
select (fn) {
const newOps = this.operations.slice();
newOps.push((coll) => coll.map(fn));
return new Enumerable(this.collection.slice(), newOps);
}</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">operations</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">operations</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">operations</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">undefined</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">operations</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">slice</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">operations</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">operations</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">operations</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">map</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">operations</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">map</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">collection</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">new Enumerable</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">newOps</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">toArray</code>, который должен применить к коллекции все операции. То есть нам нужно пропустить коллекцию сквозь все функции, которые там описаны, и получить результат. Но здесь есть еще одна ошибка, которая неочевидна.</p>
<h2 id="heading-2-5">Сравнение объектов</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">[] === []; // false
['cat', 'dog'] === ['cat', 'dog'] // false</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>И то же самое касается пустых и непустых объектов:</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 a = {}
const b = {}
а === b // false</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>В этих примерах поведение одинаковое, потому что и то и то является объектом.</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">a</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">b</code> происходит создание другого объекта, несмотря на то, что он структурно тот же. Но это не тот же объект. То есть здесь понятие изменяемости вводит понятие разности.</p>
<p>Например, счет с деньгами может быть один. При этом на нем сначала то одно количество денег, то другое. Но счет все равно один. Здесь та же ситуация.</p>
<p>Причем примитивные типы являются неизменяемыми, они присваиваются и работают не по ссылке, а по значению. Их всегда можно сравнить. А все типы объектов сравниваются по ссылкам, и поэтому они так работают.</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">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">false</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> скажет, что они <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">true</code> — равны:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">assert.equal(a, b) // false
assert.deepEqual(a, b) // true</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>Есть хороший пример, по которому понятно, как это работает, и как об этом думает JavaScript.</p>
<p>Например, для потребителя неважно, какая у нас купюра, важен только ее номинал. Допустим, у нас есть 100 долларов. Если мы поменяем купюру, у нас все равно окажется 100 долларов. Для нас важно само значение. А для производителя купюр, каждая купюра имеет свой номер, и ведется соответствующий учет.</p>
<h2 id="heading-2-6">Передача по ссылке</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">const numbers = [10, 8, 1, 7, 4]
const mySort = coll => coll.sort()
mySort(numbers) // [ 1, 10, 4, 7, 8 ]
// side-effect
console.log(numbers) // [ 1, 10, 4, 7, 8]</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">numbers</code>. В этом случае происходит так называемый pass-by-reference — передача по ссылке.</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">numbers</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">sort</code> сортирует in place — перемешивает всю коллекцию и в итоге <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">numbers</code> меняются. Это и есть сайд-эффект, когда наша функция нечистая.</p>
<p>Это иногда нужно для эффективности, но желательно не писать такой код, хотя с объектами так всегда и получается.</p>
<h2 id="heading-2-7">Общий пример</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">const f = (a, b, c) => {
a = a * 10
b.item = 'changed'
с = { item: 'changed' }
}
const num = 10
const obj1 = { item: 'unchanged' }
const obj2 = { item: 'unchanged' }
f(num, obj1, obj2)
console.log(num) // 10
console.log(obj1.item) // changed
console.log(obj2.item) // unchanged</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>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">a</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">a = a * 10</code>.</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">b</code> — это объект, мы у него меняем одно поле</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">c</code> — это тоже объект, но мы его целиком заменяем на другой объект</li>
</ul>
<p>Далее вызываем функцию, которую определили выше, передаем туда эти параметры и распечатываем. Посмотрим, что же из них изменилось:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">console.log(num) // 10
console.log(obj1.item) // changed
console.log(obj2.item) // unchanged</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">num</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">10</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">a = a * 10</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">a</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">a</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">obj1.item</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">obj2.item</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">unchanged.</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">c</code>. Но внутри теперь хранится ссылка на другой объект.</p>
<p>Это означает, что если у нас были другие ссылки на исходный объект, то они остаются. При этом сам объект остается неизмененный.</p>
<h2 id="heading-2-8">Выводы</h2>
<p>В этом уроке мы познакомились еще с одной особенностью, которая касается изменяемых данных. Это так называемые ссылки. Мы узнали, что такое Lazy Evaluation. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.</p>
<p>Также мы узнали, что ленивые вычисления помогают при работе с ленивыми коллекциями. Для практически любого языка написано большое количество подобных коллекций. Они позволяют работать с бесконечными списками и строить гораздо более эффективную обработку.</p>
<p>Еще мы разобрали, как добавлять ленивые функции в LINQ и как сравнивать пустые и непустые объекты.</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/lazy/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/lazy/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>