<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Vadym Ohyr</title><generator>teletype.in</generator><description><![CDATA[Vadym Ohyr]]></description><image><url>https://img2.teletype.in/files/51/45/51454fdc-842e-4750-96a3-bf336ea1c1a5.jpeg</url><title>Vadym Ohyr</title><link>https://blog.ohyr.dev/</link></image><link>https://blog.ohyr.dev/?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/dusty3ntity?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/dusty3ntity?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Wed, 06 May 2026 12:07:45 GMT</pubDate><lastBuildDate>Wed, 06 May 2026 12:07:45 GMT</lastBuildDate><item><guid isPermaLink="true">https://blog.ohyr.dev/sdlc</guid><link>https://blog.ohyr.dev/sdlc?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/sdlc?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>Все про SDLC</title><pubDate>Sat, 20 Aug 2022 12:17:42 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/9b/72/9b72cd7c-ee35-41e1-9d44-46b3492dd8e6.png"></media:content><category>Management</category><description><![CDATA[<img src="https://img3.teletype.in/files/69/54/6954e666-16ab-4d9a-8748-77a29df8e340.png"></img>Колись давно почали створювати програмні продукти на рівні бізнесу. А бізнес же непростий, для нього треба розробити якісь процеси, все розпланувати і підрахувати та мінімізувати ризики. Розробка ПЗ - не виключення, тож так і зʼявився SDLC - Software Development Life Cycle.]]></description><content:encoded><![CDATA[
  <p id="va52">Колись давно почали створювати програмні продукти на рівні бізнесу. А бізнес же непростий, для нього треба розробити якісь процеси, все розпланувати і підрахувати та мінімізувати ризики. Розробка ПЗ - не виключення, тож так і зʼявився SDLC - Software Development Life Cycle.</p>
  <figure id="cr8W" class="m_column">
    <img src="https://img3.teletype.in/files/69/54/6954e666-16ab-4d9a-8748-77a29df8e340.png" width="816" />
  </figure>
  <p id="Tni6">По суті, SDLC - це деякий цикл, в якому кожен етап впливає на дії, що будуть виконані у всіх наступних етапах, таким чином направляючи розробку у правильне русло.</p>
  <p id="ePgx">Отже, існує кілька моделей цього циклу, деякі менш популярні, деякі - найбільш. Зараз коротко пройдемося по ним.</p>
  <p id="qd7y"></p>
  <h2 id="433N">Waterfall</h2>
  <p id="4ehc">Завжди починають саме з неї, бо ця модель є найпершою структурованою.</p>
  <figure id="yOvt" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de8b87fcbd7c75997bc_Waterfall.png" width="1120" />
    <figcaption>Етапи Waterfall моделі</figcaption>
  </figure>
  <p id="tZcz">Це <strong>лінійна</strong> модель, суть якої полягає у тому, що наступний етап не може початись, поки не закінчився попередній. Це ж є і її найбільшим недоліком: зараз практично не буває таких проектів, у яких, наприклад, не змінюються вимоги під час розробки.</p>
  <p id="AaA4">Плюси моделі Водоспаду такі:</p>
  <ul id="Jm0J">
    <li id="oQY7">Проста як двері. Її дуже просто пояснити клієнту і користувачам.</li>
    <li id="ZFCs">Оскільки не виконавши попередній етап, не можна перейти до наступного, перед початком нового етапу завжди створюється досить непогана <strong>документація</strong>.</li>
    <li id="lPQa">Команда зфокусована лише на одному етапі одночасно.</li>
  </ul>
  <p id="rfB2">Але, мінуси все ж переважають:</p>
  <ul id="DcZf">
    <li id="M318">Не передбачає повернення до попередніх етапів, тобто, після зміни вимог вже під час розробки модель просто <strong>ламається</strong>.</li>
    <li id="pgKc">Передбачена лише для <strong>маленьких</strong> і &quot;<strong>одноразових</strong>&quot; проектів, де розробка робиться лише <em>once and forever</em>.</li>
    <li id="NslM">Не передбачає жодних прототипів на старті - працюючий продукт виходить лише ближче до кінця процесу.</li>
    <li id="aim5">Якщо при розробці виявили <strong>проблеми</strong>, їх можна починати фіксити лише на <strong>наступному</strong> етапі - Testing.</li>
  </ul>
  <p id="tejB">Висновок. Модель рекомендується лише для маленьких короткотривалих проектів без ризиків зміни вимог після їх визначення.</p>
  <p id="hZQ9"></p>
  <h2 id="f1TI">V-Model</h2>
  <p id="5kv0">Ця <strong>лінійна</strong> модель (ще називають <em>Verification and Validation Model</em>) є невеликим розширенням Водоспаду: для кожного етапу до кодування передбачається окремий етап дизайну відповідних тестів: </p>
  <figure id="Ag1c" class="m_column" data-caption-align="center">
    <img src="https://images.squarespace-cdn.com/content/v1/58d0255b8419c272379ca332/1617663965929-UN6WJ936JKG1PFT1EH84/verification-validation.png" width="1511" />
    <figcaption>Етапи Verification-Validation моделі</figcaption>
  </figure>
  <p id="8qzE">Таким чином, вже одразу після етапів збору вимог, дизайну системи, архітектурного дизайну ми робимо дизайн відповідних тестів: User Acceptance tests, Functional tests, Integration tests і Unit tests відповідно.</p>
  <p id="vsLu">В принципі, переваги і недоліки у V-Model такі ж, як і у Водоспада. Також рекомендується використовувати лише для маленьких і &quot;зрозумілих&quot; проектів.</p>
  <p id="15ij"></p>
  <h2 id="MLFf">Ітеративна модель</h2>
  <p id="zDMD">Ця модель вже дещо популярніша від попередніх, дехто навіть може використовувати її сьогодні.</p>
  <p id="1j9B">Суть моделі полягає у тому, щоб розбити розробку продукту на ітерації, кроки в яких - однакові:</p>
  <figure id="hBsJ" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de8b16c6ea286d2cf35_Iterative-model.png" width="1120" />
    <figcaption>Етапи Ітеративної моделі</figcaption>
  </figure>
  <p id="XgGW">Таким чином, Ітеративна модель дещо нагадує Agile: планування, робота з вимогами, розробка і тестування є складовими кожної ітерації. Головним є те, що етапи можуть виконуватись <strong>одночасно</strong>, як-от розробка тест-кейсів і кодування, а також можливе <strong>повернення</strong> до попереднього етапу.</p>
  <p id="haJf">Отже, плюси такі:</p>
  <ul id="cymg">
    <li id="JDjf">Для початку роботи не вимагається наявність всіх вимог до продукту.</li>
    <li id="CzVJ">Досить швидко зʼявляється прототип.</li>
    <li id="t3ZX">Тестування і відладка в маленьких ітераціях набагато легші.</li>
    <li id="INSG">Модель підтримує зміну вимог.</li>
    <li id="lMyT">Можемо виміряти прогрес.</li>
    <li id="W1hV">Підходить для великих проектів.</li>
    <li id="oo2E">Дозволяє засетапити кілька команд для <strong>паралельної</strong> розробки.</li>
  </ul>
  <p id="opQo">І мінуси:</p>
  <ul id="4fmm">
    <li id="zpBC">Треба більше ресурсів, щоб правильно керувати процесом при переході між ітераціями.</li>
    <li id="93qj">Оскільки потребує багато ресурсів, не підходить для малих проектів.</li>
  </ul>
  <p id="G4X7"></p>
  <h2 id="gEQ4">Спіральна модель</h2>
  <p id="d3WD">Спіральна модель - це покращена Ітеративна модель. Ця методологія поєднує у собі ітеративний підхід з <strong>лінійним</strong> Водоспадом - для оцінки ризиків, що підходить для дуже великих і ризикових продуктів. По суті, модель поділяється на спіралі (ітерації), які у свою чергу поділяються на фази.</p>
  <figure id="AwqK" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de80ef2f70f01846b8c_Spiral.png" width="1120" />
    <figcaption>Етапи Спіральної моделі</figcaption>
  </figure>
  <p id="WkIY">Отже, модель має 4 фази:</p>
  <ol id="R4e9">
    <li id="R2i4"><strong>Identification</strong>. Саме з цієї фази починається кожна спіраль. Включає збір вимог до продукту, а також багато комунікації, метою якої є повна зрозумілість вимог всім сторонам. У другій і наступних спіралях включає збір вимог для систем, підсистем, модулів і т.д. </li>
    <li id="s7Wp"><strong>Design</strong>. На першій спіралі у цій фазі створюється концептуальний дизайн продукту. На наступних спіралях - архітектурний дизайн, дизайн підсистем, модулів і т.д.</li>
    <li id="MJEK"><strong>Construction</strong>. У цій фазі проводиться розробка і тестування продукту. На першій спіралі ж розробляється POC (Proof of Concept).</li>
    <li id="EzSC"><strong>Evaluation</strong>. І нарешті - оцінка зробленого самим замовником, що включає також оцінку ризиків - як саме варто продовжувати розробку, чи взагалі варто, що треба врахувати у майбутньому і т.д.</li>
  </ol>
  <p id="erXu">Ключовими відмінностями від Ітеративної моделі є те, що у Спіральній неможливо повернутися до попереднього етапу, коли наступний уже почався, а також те, що  тестування у спіральній моделі виконується лише після кодування. Коротше, запозичення з Водоспаду.</p>
  <p id="CS4H"></p>
  <h2 id="ZKtF">Agile</h2>
  <p id="prIW">І, нарешті, найсучасніша методологія - Agile. Хоча, насправді, Agile - це набір практик, який успадковують його фреймворки: Scrum, Kanban, XP... </p>
  <figure id="h0gT" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de835be26f0e7585ffe_Agile.png" width="1120" />
    <figcaption>Ілюстрація етапів Agile моделей</figcaption>
  </figure>
  <p id="cjKP">Тож у чому переваги Agile? Ну, скажімо, це найбільш оптимальний підхід до розробки програмних продуктів, який фокусується на <strong>унікальності</strong> кожного окремого продукту. Так, Agile знову ж пропонує поділити розробку на <strong>ітерації</strong>, але тут вони зазвичай дуже короткі: від одного до трьох тижнів. Після кожної ітерації виходить робочий продукт, і проводиться демо для стейкхолдерів.</p>
  <p id="lQGA">Роком народження Agile вважають 2001, коли у мережі опублікували так-званий <a href="https://agilemanifesto.org/" target="_blank">The Agile Manifesto</a>, хоча його імплементації зʼявились раніше. Цей маніфест каже, що треба більше цінувати:</p>
  <ul id="d4Or">
    <li id="XaXb"><strong>Людей, а не процеси</strong>: в Agile самоорганізація і взаємодія є важливими.</li>
    <li id="Wbj0"><strong>Працюючий продукт, а не гарну документацію</strong>. Тут мається на увазі, що найкращий спосіб комунікації між клієнтом і розробниками - демо, а не текст. Технічну документацію ніхто не відміняє!</li>
    <li id="zGFB"><strong>Співпрацю з клієнтом, а не роботу над контрактом</strong>: клієнт має бути постійно залученим у процес розробки для узгодження і коригування вимог.</li>
    <li id="9S2h"><strong>Реагування на зміни, а не дотримання плану</strong>: запити на зміни мають бути почутими і швидко задоволеними.</li>
  </ul>
  <p id="xkx8">Також Agile вводить такі ролі:</p>
  <ul id="uyrE">
    <li id="Fxn3"><strong>Product Owner</strong> (ще називають <em>&quot;голос клієнта&quot;</em>) - є &quot;власником&quot; вимог до продукту і відповідальною за їх узгодження і валідацію особою. Постійно комунікує з командою розробки, працюючи над так-званими <em>User Stories</em> - тасками з точки зору бізнесу.</li>
    <li id="0dVV"><strong>Scrum Master</strong> - це по суті роль менеджера, який наглядає за проектом і перевіряє, чи всі Agile принципи, процеси і цінності дотримуються командою розробки.</li>
    <li id="myWf"><strong>Team Member</strong> - всі інші члени команди, як-от девелопер, куа, бізнес аналітик, дизайнер і т.д.</li>
  </ul>
  <p id="4F3K">Отож, як я вже сказав, Agile - то лише звід практик, а моделями SDLC є саме фреймворки Agile: <strong>Scrum</strong>, <strong>Kanban</strong>, <strong>Extreme Programming</strong>... І проект може як обирати між цими фреймворками, так і комбінувати їх. Звісно ж, всі ці моделі створені саме для великих і довгих (навіть нескінченних) проектів, де точні вимоги до фінального продукту формуються досить довго.</p>
  <p id="WS7g"></p>
  <h2 id="rZxz">Scrum</h2>
  <p id="jp1S">Scrum - це один з найвідоміших фреймворків Agile. </p>
  <figure id="1CGZ" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de969963446e4670e80_Scrum.png" width="1120" />
    <figcaption>Ілюстрація роботи Scrum моделі</figcaption>
  </figure>
  <p id="Mo7i">По суті, все те ж саме, що і диктує Agile, але конкретніше. За Скрамом, ітерації поділяються на такі етапи:</p>
  <ul id="9tfj">
    <li id="0Hhg"><strong>Planning</strong>: визначаються пріоритети спринта і формується скоуп тасок на цей спринт.</li>
    <li id="3kN0"><strong>Daily standup</strong>: всім нам знайомий дейлік, де кожен має відповісти на питання &quot;що робили вчора&quot;, &quot;що будете робити сьогодні&quot; і &quot;які є блокери&quot;.</li>
    <li id="ixYk"><strong>Demo meeting</strong>: демо з кастомером, на якому демонструється робота, зроблена за спринт, і кастомер ділиться фідбеком.</li>
    <li id="iijK"><strong>Retrospective meeting</strong>: є багато форматів, але в загальному тут команда підводить підсумки спринта і кожен має можливість поділитись тим, що йому сподобалось і не сподобалось протягом роботи над цим спринтом, щоб у наступному спринті це покращити.</li>
  </ul>
  <p id="1gFv">Варто також зазначити, що ще є <strong>Grooming meeting</strong> (ще називають <em>Backlog Grooming</em>) - це такий мітинг, де команда проходиться по якійсь частині беклогу, оцінюючи сторі, а також намагаючись максимально продумати (прогрумити) їх, позбавившись будь-яких неточностей чи незрозумілостей.</p>
  <p id="p2C2"></p>
  <h2 id="XvdC">Kanban</h2>
  <p id="gssU">Також дуже популярний фреймворк Agile, який часто напряму порівнюють зі Скрамом.</p>
  <figure id="WXeL" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de988608d667e10c52f_Kanban.png" width="1120" />
    <figcaption>Ілюстрація роботи Kanban моделі</figcaption>
  </figure>
  <p id="ZaDG">Головна відмінність Канбану від Скраму полягає у тому, що в Канбані здебільшого немає естімейшина а також фокусу на <strong>тривалості</strong> ітерацій: в той час як у Скрамі всі ітерації є фіксованими за часом, Канбан таких обмежень не має.</p>
  <blockquote id="f8dq">До речі, Канбан вийшов з компанії Toyota, а сам цей термін з японської перекладається як &quot;вивіска з лого компанії&quot;.</blockquote>
  <p id="1yPC">Саме Канбан завжди асоціюється з цими дошками з колонками, що відповідають статусу тікетів, які рухаються з колонки до колонки. Така модель ідеально підходить проектам, де непросто оцінити складність тікетів і, відповідно, проестімейтити їх у сторіпоінтах - як на моєму проекті раніше: коли два однакових тікети можуть важити як 2 сторіпоінти, так і 20.</p>
  <p id="7tNB"></p>
  <h2 id="Y14X">Extreme Programming (XP)</h2>
  <p id="9syO">Цю методологію я ще не зустрічав на реальних проектах, але багато чув про неї.</p>
  <figure id="qLXb" class="m_column" data-caption-align="center">
    <img src="https://assets-global.website-files.com/60494527fea68422687bfcf1/60521de967ba85bf2c2959f3_XP.png" width="1120" />
    <figcaption>Ілюстрація роботи моделі Extreme Programming</figcaption>
  </figure>
  <p id="qbky">По суті, модель фокусується на кількох речах:</p>
  <ul id="EXPm">
    <li id="dZmu"><strong>Парне програмування</strong>: вся девелопмент робота виконується у парах, як кажуть &quot;один - driver, інший - navigator&quot;. Передбачається, що якщо писати код у парі, багів буде значно менше, а процес код ревью взагалі не потрібен. Також обовʼязково має бути зміна ролей у процесі розробки (ну тобто спочатку один пише код а інший &quot;навігує&quot;, через деякий час ролі змінюються).</li>
    <li id="PLep"><strong>Тести</strong>: весь код має бути покритий надійними юніт- і функціональними тестами, щоб ще більше зменшити імовірність появи багів, а також забезпечити можливість перевикористання коду.</li>
    <li id="tOGA"><strong>Комунікація</strong>: знову ж, посилена комунікація між членами команди і стейкхолдерами, ледь не до сумісного написання коду.</li>
  </ul>
  <p id="8ZaA">У цієї методології також є свій <a href="http://www.extremeprogramming.org/when.html" target="_blank">офіційний сайт</a>, де можна почитати про неї детальніше.</p>
  <p id="LqHu"></p>
  <h2 id="kLsx">Висновки</h2>
  <blockquote id="vHpC">Software Development Life Cycle - цикл розробки ПЗ, який потрібен <strong>бізнесу</strong> для планування і керування процесом розробки, а також для оцінки продуктивності і ризиків. Етапи SDLC найчастіше включають збір і валідацію вимог, планування, дизайн, кодування, тестування і підтримку (або deployment).</blockquote>
  <blockquote id="rPFg">Існує багато моделей (методологій) SDLC, найбільш відомі з яких: Waterfall, V-модель, Ітеративна, Спіральна, а також Agile.</blockquote>
  <blockquote id="2M8T">Waterfal - найперша структурована <strong>лінійна</strong> модель SDLC, яка передбачає те, що кожен наступний етап розробки може початись лише після <strong>закінчення</strong> попереднього. Підходить лише для маленьких проектів, де одразу є зібрані вимоги, що всім очевидні і не будуть змінюватись впродовж розробки.</blockquote>
  <blockquote id="hmKi">V-модель - невелике покращення Водоспаду, яке полягає у тому, що розробка тест-кейсів відбувається одразу ж після відповідних етапів: User-Acceptance, Functional, Integrational, Unit.</blockquote>
  <blockquote id="a0QL">Ітеративна модель - принципово нова модель, яка вводить поділ процесу розробки на <strong>ітерації</strong>, які складаються з однакових етапів. При цьому є можливість повертатися до <strong>попередніх</strong> етапів у разі необхідності. Це дає можливість застосувати модель для сучасних і довготривалих проектів, де повні вимоги до початку проекту ще не відомі і можуть змінюватись.</blockquote>
  <blockquote id="ddAW">Спіральна модель - це <strong>комбінація</strong> Водоспаду і Ітеративної моделі, яка допомагає краще оцінювати <strong>ризики</strong>. Розробка також ділиться на ітерації (спіралі), які у свою чергу діляться на фази. Головна відмінність від Ітеративної моделі - не можна переходити до попереднього етапу після його закінчення. Також, тестування виконується лише після кодування.</blockquote>
  <blockquote id="68sA">Agile - звід <strong>практик</strong> для моделей SDLC, який створений з урахуванням того, що кожен проект <strong>унікальний</strong>. Передбачає поділ розробки на ітерації (зазвичай з фіксованою тривалістю). Agile передбачає, що: люди і самоорганізація є важливішими за процеси; жива комунікація між командою і клієнтом краща, ніж письмова документація; зміни і побажання клієнта мають бути швидко враховані і реалізовані.</blockquote>
  <blockquote id="4hjC">Scrum - фреймворк Agile, який передбачає <strong>спринти</strong> - ітерації тривалістю у кілька тижнів, <strong>демо</strong> як комунікацію між клієнтом і командою і можливість продемонструвати виконану роботу, а також <strong>ретроспективи</strong> - зустрічі для виявлення проблем, що мали місце протягом останнього спринта.</blockquote>
  <blockquote id="GKPH">Kanban - фреймворк Agile, що на відміну від Scrum, не має <strong>естімейшина</strong> тасок у сторіпоінтах і не передбачає <strong>фіксованих</strong> за часом ітерацій. Kanban фокусується на статусах тікетів і їх менеджменті через славетну Kanban-дошку.</blockquote>
  <blockquote id="JiIc">Extreme Programming - фреймворк Agile, що фокусується на парному програмуванні і якісних тестах.</blockquote>
  <p id="VCg6"></p>
  <h2 id="42T3">Джерела</h2>
  <p id="03dq"><a href="https://www.tutorialspoint.com/sdlc/index.htm" target="_blank">https://www.tutorialspoint.com/sdlc/index.htm</a></p>
  <p id="Wejd"><a href="https://www.roberthalf.com/blog/salaries-and-skills/6-basic-sdlc-methodologies-which-one-is-best" target="_blank">https://www.roberthalf.com/blog/salaries-and-skills/6-basic-sdlc-methodologies-which-one-is-best</a></p>
  <p id="7J36"><a href="https://www.virtasant.com/blog/sdlc-methodologies" target="_blank">https://www.virtasant.com/blog/sdlc-methodologies</a></p>
  <p id="0moz"><a href="https://www.javatpoint.com/software-engineering-software-development-life-cycle" target="_blank">https://www.javatpoint.com/software-engineering-software-development-life-cycle</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ohyr.dev/kiss-yagni-dry</guid><link>https://blog.ohyr.dev/kiss-yagni-dry?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/kiss-yagni-dry?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>KISS, DRY і YAGNI</title><pubDate>Thu, 04 Aug 2022 21:00:55 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/58/69/5869f771-1433-43ab-aaba-0be1deb63f2d.png"></media:content><category>Principles</category><description><![CDATA[<img src="https://img3.teletype.in/files/25/4c/254ca56d-599c-43a4-85d6-3fbf0e23d2ee.jpeg"></img>На мою думку, ці принципи є найбільш вживаними &quot;універсальними&quot; принципами програмування, які є такими-собі standalone: якщо SOLID все-таки більше застосовують до механік ООП, то KISS, DRY і YAGNI можна застосувати до будь-чого, навіть не обмежуючись мовами програмування - до HTML, наприклад.]]></description><content:encoded><![CDATA[
  <figure id="0CDW" class="m_column" data-caption-align="center">
    <img src="https://img3.teletype.in/files/25/4c/254ca56d-599c-43a4-85d6-3fbf0e23d2ee.jpeg" width="1200" />
    <figcaption>Порядок роботи принципів на прикладі</figcaption>
  </figure>
  <p id="zze5">На мою думку, ці принципи є найбільш вживаними &quot;<strong>універсальними</strong>&quot; принципами програмування, які є такими-собі <strong>standalone</strong>: якщо <em>SOLID</em> все-таки більше застосовують до механік ООП, то <em>KISS</em>, <em>DRY</em> і <em>YAGNI</em> можна застосувати до будь-чого, навіть не обмежуючись мовами програмування - до HTML, наприклад.</p>
  <p id="k6NG">Тож давайте розглянемо тут ці принципи, щоб потім можна було флексити на співбесідах.</p>
  <p id="EY71"></p>
  <h2 id="rHoq">KISS</h2>
  <p id="nDmw">Принцип KISS розшифровується як <em>Keep it Simple, Stupid</em>,проте інколи його вживають або просто як <em>Keep it Simple</em> (щоб не називати людей stupid), або ж <em>Keep it Short and Simple</em>.</p>
  <p id="0rlr">Офіційне <strong>формулювання</strong>:</p>
  <blockquote id="WI9w">Більшість систем працюють краще, якщо вони залишаються <strong>простими</strong>, а не <strong>ускладнюються</strong>. Саме тому простота має бути однією з ключових цілей в програмуванні, і варто уникати надмірної складності.</blockquote>
  <p id="xtqi">По суті, так, якщо вас спитають про цей принцип, достатньо сказати, що прості системи працюють краще і надійніше, ніж складні. Тепер давайте поговоримо про інші трактування і applications цього принципу, в тому числі у фронтенді.</p>
  <p id="GN2I">1. Не треба для якоїсь <strong>маленької фічі</strong> додавати у проект цілу <strong>велику бібліотеку</strong>. Надзвичайно розповсюджено зараз, особливо на фронті - як-от з бібліотекою <a href="https://momentjs.com/" target="_blank">moment.js</a>. Нам треба одну дату в проекті відформатувати, а ми додаємо цілу бібліотеку, тим самим додаючи цілу <strong>одну залежність</strong>, збільшуючи <strong>розмір бандлу</strong>, збільшуючи кількість потенційних <strong>вразливостей</strong> у проекті і так далі.</p>
  <p id="eoZo">2. (<em>перетинається з YAGNI</em>). Не варто перевантажувати інтерфейс - надмірно розбивати один метод на купу інших, виносити певний хардкод в конфігураційні файли, додавати можливість налаштування чомусь простому...<br />Приклад. Є у нас бекенд, який запускається на строго визначеному порті (ну максимум додали можливість задавати порт у конфіг-файлі). І ми такі &quot;ну блін, це ж не зовсім зручно, давайте напишемо логіку для автоматичного знаходження першого вільного порта і будемо біндитись туди, це ж краще&quot;. Ну зробили так. А потім адміни голову ламають, як наш бекенд порт вибирає собі, мають налаштовувати фаєрвол для всіх портів одразу і т.д. По-перше, не варто було так робити - треба було по-простому - конфіг файл і все. По-друге, якби клієнту знадобився такий автовибір порту, він би сам цю логіку на своїй стороні написав.</p>
  <p id="rIlm">3. Не потрібно <strong>оптимізовувати</strong> проект, економлячи кілька наносекунд. Швидш за все, ця оптимізація зробить з коду кашу, і він точно не буде <em>short and simple</em>. До цього ж відноситься і реалізація астрономічної <strong>точності</strong> <strong>математичних</strong> <strong>розрахунків</strong> на неастрономічних проектах.</p>
  <p id="jLNT">4. Що більше стрічок коду у проекті, то більше в ньому потенційних багів, тому що кількість багів у проекті прямо пропорційна кількості стрічок коду.</p>
  <p id="wImn">5. Насправді, кожен початківець стикається з результатом &quot;невиконання&quot; цього принципу тоді, коли <strong>начитавшись розумної літератури</strong>, починає новий проект. Стільки всяких ідей крутиться в голові, треба ж зробити так, щоб у майбутньому було простіше розширяти проект, треба продумати всі моменти... Так, якщо цього не зробити, то у вас вийде <strong>Фейсбук</strong>, технічний борг у якому хрін випилиш. Але якщо упоротись - ви проект так і <strong>не закінчите</strong>. Тому варто шукати золоту середину.</p>
  <p id="XkT6">До речі, цікавий факт: <em>принцип зʼявився у 1960 році в колах ВМС США</em>. </p>
  <p id="jKAa"></p>
  <h2 id="xoI3">YAGNI</h2>
  <p id="xq9R">Розшифровується як <em>You Aren&#x27;t Gonna Need It</em>.</p>
  <p id="xth6">Офіційне <strong>формулювання</strong>:</p>
  <blockquote id="mBqN">Можливості, які не<strong> описані</strong> у вимогах системи, не повинні бути <strong>реалізовані</strong>.</blockquote>
  <p id="Tzxy">Тобто коли пишемо код, треба бути впевненими, що цей код обовʼязково нам <strong>знадобиться</strong>. Якщо думаємо - що код знадобиться <strong>пізніше</strong> - негайно припиняємо. Якщо ж займаємось рефакторингом - не варто боятись <strong>видаляти</strong> сутності, що не використовуються - раніше були часи, коли ці сутності були потрібні, тепер ці часи минули.</p>
  <p id="Xs4v">Ну і знову ж, поговоримо про реальні приклади і застосування цього принципу.</p>
  <p id="prGX">1. Замовник не має <strong>оплачувати</strong> те, чого він не<strong> замовляв</strong>. Навіть якщо ви думаєте, що якась штука буде йому корисною, він має про це знати ще до того, як ви почнете її писати, бо, повірте, у майбутньому це гарантовано вилізе боком.</p>
  <p id="YEaC">2. Поговоримо про &quot;вилізе боком&quot; з попереднього пункту. Ну от ви написали якусь додаткову логіку &quot;на майбутнє&quot;. Потім замовник вам дає завдання написати нову фічу, і ви такі: &quot;блін, вона ж повністю <strong>протиречить</strong> тій додатковій логіці, що я написав раніше...&quot;. І це ще ок, ви просто видалите її. Але ж може бути, що ви написали новий модуль типу &quot;на майбутнє&quot;, і вже використали його десь. Так тепер вам ще і попереднє все переписувати, перетестовувати і так далі.</p>
  <p id="uusq">3. Написавши нову штуку, на вас лягає зобовʼязання її <strong>підтримувати</strong>: писати тести, документацію, підганяти конфігурацію і слідкувати за помилками, пояснювати юзерам, як вашою штукою користуватись, і так далі. Воно вам треба?</p>
  <p id="SxfZ">4. Якщо проект fixed-price, а ви додали якусь нову штуку, якої не було в ТЗ, замовник з легкістю може почати вами <strong>маніпулювати</strong>: &quot;ой, а ви ось це зробили, але воно взагалі не так повинно працювати, допишіть будь ласка&quot;. Таким чином, ви додали роботи, тобто витратили гроші вашої компанії, і цілком можливо навіть вивели її в мінус, тому варто чекати небажаних гостей.</p>
  <p id="XHgY"></p>
  <h2 id="ASFX">DRY</h2>
  <p id="YNBi">Ну тут все просто: <em>Don&#x27;t Repeat Yourself</em>. Принцип був сформований у відомій книзі &quot;Програміст-прагматик&quot; Ендрю Ханта і Дейва Томаса. Насправді, цей принцип не стільки про <strong>дублювання коду</strong> (бо про це і так каже майже будь-який інший принцип), скільки про <strong>єдине джерело даних</strong> (<em>Single Source of Truth</em>).</p>
  <p id="owsW">Офіційне <strong>формулювання</strong>:</p>
  <blockquote id="6wC5">Будь-яка інформація повинна мати єдине, однозначне і авторитетне <strong>представлення</strong> в системі.</blockquote>
  <p id="KOxb">Безліч разів бачив, що дані в додатку дублюються просто для того, щоб було &quot;простіше працювати&quot;, типу дані однакові, але зберігаються в різних форматах. Я теж коли писав свої проекти, думав: &quot;блін, нашо мені кожен раз конвертувати цю стрічку в мій формат, якщо можна просто в таблиці поруч нове поле зробити і туди це все класти&quot;. Насправді, нове поле поруч з оригінальним - це ще не страшно, бо його видно. А ось коли у нас одні і ті самі дані в різних таблицях - це вже страшно. Та, насправді, навіть коли вони поруч, дуже просто забути продублювати операції для роботи з ними. У будь-якому разі, рано чи пізно ці дані <strong>розсинхронізуються</strong>, і стане погано.</p>
  <p id="qGFs">Так, принцип також застосовують до коду, але я хотів би тут перерахувати ті виключення, при яких цей принцип можна і треба <strong>не виконувати</strong>.</p>
  <p id="saEh">Ну ось наприклад ви пишете фронт і бек - валідація. Її просто обовʼязково дублювати, це загальновідоме правило і без цього ніяк не можна, бо інакше ви або змушуєте клієнт і сервер страждати від величезної кількості запитів (якщо валідувати тільки на бекенді), або ж порушуєте найпростіші принципи компʼютерної безпеки (валідуючи лише на фронті).</p>
  <p id="6l0Z">Також як чудовий приклад виключення з принципу DRY можна назвати <strong>компʼютерні ігри</strong>. Зазвичай у онлайн-іграх деякий код дублюють і на клієнті, і на сервері, заради оптимізації. Адже без такої оптимізації навіть гравці з відносно непоганим пінгом бачили б слайдшоу замість плавного руху персонажів, до прикладу (ну типу клієнт сам обраховує приблизні вектори руху персонажів і рухає їх, поки достовірна інформація йде з сервера, і виходить імітація відсутності затримок).</p>
  <p id="xq2k"></p>
  <h2 id="uP4T">Висновки</h2>
  <blockquote id="gCqa">KISS (<em>Keep It Simple, Stupid</em>): більшість систем працюють <strong>краще</strong>, якщо вони залишаються <strong>простими</strong>, а не <strong>ускладнюються</strong>. Суть: треба писати <strong>по-дубовому</strong>, не створювати мільйон абстракцій і конфігурацій. Також не варто лише задля <strong>маленької</strong> фічі запихати в проект <strong>величезну</strong> бібліотеку.</blockquote>
  <blockquote id="vafm">YAGNI (<em>You Aren&#x27;t Gonna Need It</em>): можливості, які не<strong> описані</strong> у вимогах системи, не повинні бути <strong>реалізовані</strong>. Суть: не варто реалізовувати те, що ви думаєте, полегшить роботу або знадобиться <strong>у майбутньому</strong>.</blockquote>
  <blockquote id="pt85">DRY (<em>Don&#x27;t Repeat Yourself</em>): будь-яка інформація повинна мати єдине, однозначне і авторитетне <strong>представлення</strong> в системі. Суть: не допускайте ситуацій, коли ви зберігаєте <strong>одні і ті ж дані</strong> (просто в різних формах) у <strong>різних місцях</strong> в системі.</blockquote>
  <p id="r2RA"></p>
  <h2 id="Bf8R">Джерела</h2>
  <p id="xc9y">Коротко про KISS, YAGNI і DRY на Хабрі: <a href="https://habr.com/ru/company/itelma/blog/546372/" target="_blank">https://habr.com/ru/company/itelma/blog/546372/</a></p>
  <p id="k43Q">KISS by Sergey Nemchinskiy: <a href="https://www.youtube.com/watch?v=rix-fkrloq4" target="_blank">https://www.youtube.com/watch?v=rix-fkrloq4</a></p>
  <p id="9b4c">YAGNI by Sergey Nemchinskiy: <a href="https://www.youtube.com/watch?v=Ot2eB07rjcI" target="_blank">https://www.youtube.com/watch?v=Ot2eB07rjcI</a></p>
  <p id="viKO">DRY by Sergey Nemchinskiy: <a href="https://www.youtube.com/watch?v=NWemqNMCesQ" target="_blank">https://www.youtube.com/watch?v=NWemqNMCesQ</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ohyr.dev/solid</guid><link>https://blog.ohyr.dev/solid?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/solid?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>SOLID</title><pubDate>Thu, 28 Jul 2022 20:25:34 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7c/0f/7c0ff619-066f-4a09-b2ef-e78d2ccd9fad.png"></media:content><category>Principles</category><description><![CDATA[<img src="https://narodna-pravda.ua/wp-content/uploads/2017/09/space.jpg"></img>Щоб бути SOLIDним розробником, необхідно знати (а краще ще і використовувати в роботі) так звані принципи SOLID. ]]></description><content:encoded><![CDATA[
  <p id="d0T1">Щоб бути <strong>SOLID</strong>ним розробником, необхідно знати (а краще ще і використовувати в роботі) так звані принципи SOLID. </p>
  <p id="EE8n">Спочатку трохи історії. <strong>Роберт Мартін</strong>, відомий автор книжок про обʼєктно-орієнтоване програмування, все своє життя збирав (і сам виводив) деякі принципи, що використовуючи їх у розробці програмного забезпечення, можна було зробити його краще з точки зору <strong>коду і</strong> <strong>архітектури</strong>. А потім просто деякий <strong>Michael Feathers</strong> помітив, що назви &quot;<strong>загальних</strong>&quot; принципів Мартіна складаються в акронім - SOLID. Так вони і стали відомими.</p>
  <p id="3quh">Тож, що ж це за &quot;загальні&quot; принципи:</p>
  <ol id="X69y">
    <li id="bE2l">S - SRP - <strong>Single Responsibility Principle</strong></li>
    <li id="UtZd">O - OCP - <strong>Open-Closed Principle</strong></li>
    <li id="kMjI">L - LSP - <strong>Liskov Substitution Principle</strong></li>
    <li id="ISBD">I - ISP - <strong>Interface Segregation Principle</strong></li>
    <li id="s4oP">D - DIP - <strong>Dependency Inversion Principle</strong></li>
  </ol>
  <p id="VLD4"></p>
  <h2 id="eH7p">Single Responsibility Principle</h2>
  <p id="ruJj">На мою думку, найпростіший принцип серед усіх. Ми так чи інакше майже завжди його використовуємо на підсвідомому рівні.</p>
  <p id="9Lya"><strong>Формулювання</strong> Роберта Мартіна:</p>
  <blockquote id="z0Ya">Кожний обʼєкт повинен мати одну відповідальність і ця відповідальність повинна бути повністю інкапсульована у класі.</blockquote>
  <p id="G1V7">Щоб зрозуміти цей принцип, треба зайти з іншого боку. У будь-якого програмного забезпечення є така штука, як вісь змін. Це той набір класів, методів/функцій, полів, які постійно змінюються. Ну, тобто, щось таке:</p>
  <figure id="oRMg" class="m_column" data-caption-align="center">
    <img src="https://narodna-pravda.ua/wp-content/uploads/2017/09/space.jpg" width="610" />
    <figcaption>Візуалізація вісі</figcaption>
  </figure>
  <p id="b4W3">Часто кажуть &quot;вісь змін проходить через ось ці класи&quot;.</p>
  <p id="Xf5u">Звісно, осей змін може бути кілька у великій системі. Наприклад: одна вісь змін - бізнес логіка, а друга - робота з базою даних.</p>
  <p id="gExP">Так ось за принципом SRP через ваш клас має проходити лише одна вісь змін, тобто ваш клас повинен змінюватись лише з однієї причини, що можливо тільки тоді, коли він виконує одну функцію - відповідальний тільки за одну річ.</p>
  <p id="thPB">Також, якщо ви, наприклад, за бажанням клієнта змінили в коді одну маленьку штуку, а потім виявилось, що це вплинуло на якийсь взагалі лівий модуль в додатку, і все зламалось - це і є порушення SRP.</p>
  <h3 id="Vgl8">Приклад</h3>
  <p id="JMJu">Припустимо, ви пишете софт для компанії, яка робить побутову техніку, і ваш перший продукт - духова шафа. Перша задача - навчити духову шафу тримати температуру якийсь заданий час. У вас є годинник і термометр - треба для них код написати, щоб їх використовувати в програмах для приготування їжі. І ви такий &quot;напишу я клас DateTimeTemperature, який буде все робити&quot;. Написали величенький клас, який увійшов до кодової бази компанії, і в основу духової шафи. А потом пішли на іншу роботу, бо там більшу зарплату запропонували.</p>
  <p id="hOcE">Духова шафа продається успішно, компанія почала розширятися. Вирішили зробити два нових продукти: витяжку і електрочайник. Виділили Васю и Петю і сказали Васі писати код під витяжку, а Петі - під чайник. Витяжці треба час вимірювати, щоб поставити її на півгодини, наприклад, запахи висмоктувати. Вася покопався в репозиторіях и знайшов клас DateTimeTemperature. Температура йому взагалі не треба, тому просто взяв з класу час і вставив собі. Випустили витяжку. Петя тим часом думав, як би швидше код для чайника написати. Знайшов той же клас і думає &quot;мені ж тільки температура треба&quot;. Взяв та й зкопіював собі шматок класу. Випустили чайник.</p>
  <p id="qilJ">Все йде добре, але раптом у коді духових шаф знайшли баги. Запросили команду тестувальників, як Atlassian задумались раптом про доступність. Протестували клас DateTimeTemperature - купу багів знайшли. Пофіксили. Але ж у чайниках і витяжках той самий код, та от лежить він хрін знає де. Ось так і вийшло, що в одному і тому ж коді купа багів, але в одному місці їх пофіксили, а в іншому - ні.</p>
  <p id="PR2D">Висновок: треба було зробити два окремих класи - DateTime і Temperature. Вони б увійшли до єдиної стандартної бібліотеки компанії і їх би всі використовували. А ще кожен з цих класів мав би одну відповідальність.</p>
  <h3 id="tr78">Цікаві факти</h3>
  <p id="y8r1">Є такий патерн - Active Record. Це коли у класі окрім бізнес-логіки лежить ще і код для роботи з базою даних - записати себе, знайти себе і т.д. Хоч цей патерн і повністю порушує SRP, його часто використовують. Наприклад, ruby on rails повністю побудований на Active Record. Laravel і Yii - PHP фреймворки - теж його використовують. C# так само колись був побудований на Active Record, але скоро все ж перейшов на ORM. </p>
  <p id="Bti1"></p>
  <h2 id="DxfI">Open-Closed Principle</h2>
  <p id="oHL6">Принцип трохи складніший за попередній. Як ми знаємо, розробка програмного продукту може тривати десятиліттями. А все тому, що постійно змінюються і доповнюються вимоги бізнесу до продукту, тобто постійно потрібно вносити зміни. А основна причина, через яку це буває важко чи дорого - коли маленька зміна в одній частині коду спричиняє лавину переробок у всіх інших.</p>
  <p id="qnoR"><strong>Формулювання</strong> (не Роберта Мартіна, а декого Бертрана Меєра):</p>
  <blockquote id="vYwb">Програмні сутності - класи, функції, модулі і т.д. - повинні бути <strong>відкриті </strong>для розширення і <strong>закриті </strong>для змін.</blockquote>
  <p id="d4qv">Почнемо з того, як це можливо взагалі? А все дуже просто. Якщо є нормальний програмний продукт, який протестований, якщо він нормально спроектований – додавання нової функціональності не повинно валити існуючі тести та додавати нові баги (це ж треба буде повне регресійне тестування проводити, якщо так).</p>
  <p id="k1kh">Як Бертран Мейер пропонує це реалізовувати? Припустимо ми написали клас. Коли закінчили, вішаємо на нього умовний &quot;замочок&quot;, який забороняє нам змінювати його (звісно ж крім багфіксів). І все. Якщо хочемо розширити клас, додати нову функціональність – використовуємо наслідування. При цьому дозволяється у новому класі змінювати попередній інтерфейс.</p>
  <figure id="lJfV" class="m_original" data-caption-align="center">
    <img src="https://img1.teletype.in/files/05/00/05002db7-14a9-484d-8126-4dfb2118f2da.png" width="405" />
    <figcaption>UML-діаграма Open-Closed принципу</figcaption>
  </figure>
  <p id="c4WY">Остання штука звучить тупо, адже викликає сайд-ефекти у клієнтських класів - їх також доведеться переписувати. Саме тому вже Роберт Мартін запропонував &quot;поліморфний&quot; Open-Closed принцип.</p>
  <p id="WGLZ">Поліморфний Open-Closed принцип забороняє змінювати інтерфейс. Тобто клієнтський код повинен спілкуватися з реалізацією лише через інтерфейс (який незмінний), а цей інтерфейс вже повинен реалізовуватись старою та новою реалізаціями. При цьому нова реалізація може або делегувати старій виконання якихось шматків коду, або наслідуватись від неї.</p>
  <figure id="6fnz" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/79/87/7987a4f2-ef20-4ef0-a1ea-4fe08c3f8d41.png" width="721" />
    <figcaption>UML-діаграма поліморфного Open-Closed принципу</figcaption>
  </figure>
  <p id="vviV">Оскільки поліморфний принцип більш логічний, у сучасній розробці використовують саме його.</p>
  <p id="6Qhs">На завершення скажу, що цей принцип у сучасних реаліях перетинається з Dependency Inversion, тому що найчастіше передбачає використання класів через відповідні інтерфейси, оскільки за допомогою цього досить просто розширювати функціональність класів, до прикладу, завдяки патернам Decorator або Proxy. Візьмемо, наприклад, клієнт і сервер. Додавши серверу інтерфейс (або абстракцію), до якого звертатиметься клієнт, ми спокійно між інтерфейсом та класом можемо додати аутентифікацію, авторизацію, логування, кешування тощо.</p>
  <h3 id="5YQI">Використання у фронтенді</h3>
  <p id="6v0L">Далеко не у кожному проекті є тайпскрипт, проте цей принцип має застосування і у звичайному JS. Припустимо, ми робимо компонент кнопки - проста така кнопка з іконкою всередині. Без питань зробили, юзаємо. Через деякий час треба робити нові кнопки вже з іконкою і текстом. Ну не проблема - допишемо вже існуючий компонент і додамо необовʼязковий текст. Ще через місяць прилітає таска: зробити кнопки з бейджом непрочитаних повідомлень (як на айфонах). Та все просто - додамо бейдж до вже існуючої кнопки. І так можна продовжувати ще довго.</p>
  <p id="9sj3">Уважний розробник (а тестувальник тим більше) скаже, що після кожної такої зміни компоненту у ньому можуть зʼявитись баги, і необхідно його (і всі місця, де він використовується) перетестовувати. А якщо, припустимо, ми передамо у цю кнопку лише текст з бейджом, то вийде взагалі якась фігня. Саме тому перед кожною такою зміною варто подумати, чи не краще було б створити новий компонент. Так нам не треба буде їх постійно перетестовувати, та й імовірність зловити нові баги значно зменшиться. Звісно, назви компонентів будуть трохи довші (як-от <code>IconButtonWithTextAndBadge</code>), але на мою думку це інколи навіть краще.</p>
  <h3 id="lDFB">Приклад від себе</h3>
  <p id="Xr2n">Ось я працюю над accessibility фіксом для одного маленького дропдауну в Джирі на одній сторінці. Я не можу просто зробити по-нормальному те що мені треба, бо:</p>
  <p id="vmuo">- сто пудів впадуть тести;</p>
  <p id="LBU6">- якщо впали тести, значить зламалась поточна поведінка;</p>
  <p id="JGIQ">- якщо зламалась поточна поведінка, то є висока імовірність, що зʼявились нові баги.</p>
  <p id="q98A">А тепер уявіть, що цей дропдаун використовується ще в якійсь жопі, про яку ми і гадки не маємо. А якщо у компонента немає тестів/сторібуку, то ми про це взагалі ніколи не дізнаємось! (ну, хіба що лише тоді, коли нам прилетить по дупі за наші &quot;фікси&quot;). Саме тому працюючи над кожним таким фіксом, моя задача - зробити його максимально сумісним зі старою поведінкою так, щоб впала мінімальна кількість тестів. Тож я намагаюсь реалізувати цей фікс максимально не змінюючи попередній код, а лише дописуючи нове (за можливості). Звісно, ідеальним рішенням було б зробити новий дропдаун на основі старого, вже з фіксами, і використати саме його у тому місці, яке зарепорчено в тасці - так ми і інтерфейс зберігаємо, і зводимо імовірність появи багів у інших місцях до мінімуму.</p>
  <p id="tQNk"></p>
  <h2 id="M5oO">Liskov Substitution Principle</h2>
  <p id="g9mO">Насправді досить простий принцип. Взятий у Барбари Лісков, але формулювання використовують Мартіна (а все тому, що Лісков - математикиня).</p>
  <p id="SnGX"><strong>Формулювання</strong> Роберта Мартіна:</p>
  <blockquote id="OC8r">Функції, що використовують базовий тип, повинні мати можливість використання підтипів базового типу, не знаючи про це.</blockquote>
  <p id="8H3c">Тобто, поведінка базового класу не повинна протиречити поведінці нащадків.</p>
  <h3 id="tyXk">Приклад</h3>
  <p id="sObH">Стандартний приклад - класи Rectangle і Square:</p>
  <pre id="fiDu" data-lang="typescript">class Rectangle {
  width: number
  height: number

  constructor(width: number, height: number) {
    this.width = width
    this.height = height
  }

  setWidth(width: number) {
    this.width = width
  }

  setHeight(height: number) {
    this.height = height
  }

  areaOf(): number {
    return this.width * this.height
  }
}</pre>
  <pre id="I1lz" data-lang="typescript">class Square extends Rectangle {
  width: number
  height: number

  constructor(size: number) {
    super(size, size)
  }

  setWidth(width: number) {
    this.width = width
    this.height = width
  }

  setHeight(height: number) {
    this.width = height
    this.height = height
  }
}</pre>
  <pre id="03kF" data-lang="typescript">const square: Square

square.setWidth(20) // змінює ширину і висоту, все правильно
square.setHeight(40) // теж змінює ширину і высоту, ок</pre>
  <p id="Tlin">Але якщо ми, наприклад, почнемо писати тести...</p>
  <pre id="2LcB" data-lang="typescript">function testShapeSize(figure: Rectangle) {
  figure.setWidth(10)
  figure.setHeight(20)
  assert(figure.areaOf() === 200)
  // умова не зпрацює, якщо figure — екземпляр класу Square
}</pre>
  <p id="YOby">Математично, так, квадрат - це прямоукутник, але він поводить себе інакше!</p>
  <p id="ZINs">Таким чином, Liskov Substitution Principle пропонує використання композиції замість наслідування. А якщо і наслідування, то класи-нащадки не мають протиречити базовому класу, наприклад - надавати інтерфейс <strong>вужче</strong> базового.</p>
  <p id="tJye">Ще один приклад: класи DB і MockDB. При реалізації моку для тестів, імовірно, клас буде наслідуватись від DB і реалізовувати тільки методи, необхідні для тестів, а інші - кидати NotImplementedException. Якщо прийде інший розробник і вирішить написати свої тести з використанням цього ж моку, він з великою імовірністю отримає цей NotImplementedException. Таким чином, тут клас-нащадок надає інтерфейс <strong>вужче</strong> базового.</p>
  <p id="gNGA">Ще одне <strong>формулювання</strong> від розумних дядьків:</p>
  <blockquote id="3uuL">Підклас не має вимагати від викликаючого його коду <strong>більше</strong>, ніж базовий клас, і надавати <strong>менше</strong>, ніж базовий клас.</blockquote>
  <p id="e9Pc">В даному випадку &quot;<strong>вимагати більше&quot;</strong> - це наприклад перед викликом методів робити якусь ініціалізацію. А &quot;<strong>надавати менше&quot;</strong> - замість очікуваного обʼєкту повертати null.</p>
  <p id="fgiF">До речі, правильну роботу цього принципу можна побачити на прикладі попереднього: якщо зробити кілька компонентів, які під собою використовують попредні - підставивши на сторінку замість базового компоненту один з нащадків ми не маємо побачити жодних змін.</p>
  <p id="pwBU"></p>
  <h2 id="svVE">Interface Segregation Principle</h2>
  <p id="LwlS">Доволі простий принцип, якщо ви працюєте зі строго-типізованою мовою.</p>
  <p id="lx2G"><strong>Формулювання</strong> Роберта Мартіна:</p>
  <blockquote id="ri1z">Клієнти не мають <strong>залежати</strong> від методів, які вони <strong>не використовують</strong>.</blockquote>
  <p id="VfQ6">Це означає, що якщо у нас є інтерфейс, і його зміна призводить до змін у тих клієнтах, які щойно змінені методи навіть не використовують - принцип порушений. Суть у тому, щоб зробити кілька різних інтерфейсів, щоб будь-яка зміна у будь-якому з них не призводила до випадків, описаних вище.</p>
  <h3 id="30D9">Приклад</h3>
  <p id="Ew9I">Спочатку - стандартний приклад. Є у нас сайт з оренди нерухомості, фронт на тайпскрипті, і ми пишемо типи юзерів. У нас будуть:</p>
  <ul id="2n9a">
    <li id="S0cT">Звичайні юзери: можуть оформлювати оренду, ставити рейтинг орендодавцю і писати коменти;</li>
    <li id="rdC4">Орендодавці: можуть робити все те ж, що і звичайні юзери, плюс додавати нові оголошення;</li>
    <li id="Ac8C">Адміни: можуть робити все вищезазначене, а також видаляти коменти і банити інших юзерів.</li>
  </ul>
  <p id="f0Ef">Припустимо, що буде у нас масив з юзерами усіх видів. І ми такі бахаємо великий інтерфейс:</p>
  <pre id="Y5aD" data-lang="typescript">interface User {
    id: number,
    role: Role,
    nickname: string;
    rentApartment(apartmentId: number): void,
    setRating(apartmentId: number, rating: number): void,
    addComment(apartmentId, number, comment: string): void,
    addApartment(title: string, description: string, photo: string): void,
    removeApartment(apartmentId: number): void,
    removeComment(commentId: number): void,
    banUser(userId: number): void
}</pre>
  <p id="NlAn">Ну а потім починається:</p>
  <ul id="2sox">
    <li id="Gfwg">Нашо всім юзерам метод бану, якщо ніхто, крім адміна, цього зробити не може?</li>
    <li id="Y3s7">Як тоді визначати такі методи? Опшинал?</li>
  </ul>
  <p id="Trqt">і так далі. Правильніше зробити три інтерфейси: <strong>IUser</strong>, <strong>ILandlord</strong> і <strong>IAdmin</strong>, при цьому IUser навіть можна зробити базовим - і всі питання одразу ж відпадуть.</p>
  <h3 id="nLUy">Приклад №2</h3>
  <p id="b8r6">Більш показовим прикладом є фасад класу для роботи з БДшкою. У ньому зазвичай є абсолютно всі методи: від пошуку до операцій CRUDу. Ну ми ж гарні розробники, думаємо &quot;забахаємо інтерфейс до цього фасаду, треба ж, щоб все через інтерфейси спілкувалось&quot; і бахнули один інтерфейс для всіх методів фасаду. Настав час писати клієнтів: один використовує тільки один метод пошуку, інший використовує якийсь складніший пошук, а третій взагалі лише операції CRUDу. І всі працюють через наш інтерфейс.</p>
  <p id="uyZv">Припустимо, що інтерфейс трішки змінився, сигнатуру якогось там 340го методу змінили. І що? Правильно, після цього треба перекомпільовувати весь проект, хоча жоден з клієнтів навіть не використовує цей метод.</p>
  <p id="DgTg">Звісно, найгірше стається тоді, коли цей фасад БДшки змінюється на щось інше, наприклад - два менші фасади по 4 методи. Якби для кожного клієнта під його потреби був маленький інтерфейс - ми б швидко все поправили. А так, з одним інтерфейсом - переписуй все.</p>
  <h3 id="QSMA">Приклад №3</h3>
  <p id="FlSZ">Цей приклад більше про фронтенд. От ми юзаємо якусь бібліотеку для графіків (нехай буде <a href="https://www.highcharts.com/" target="_blank">HighCharts</a>). Використали з неї лише PieChart та і все. Через пару місяців вирішили оновити депенденсі на проекти, перед цим почитавши чейнджлог (у якому не знайшли речей, що стосуються PieChart&#x27;ів). Оновили - і все впало.</p>
  <p id="EAnV">У цьому випадку інтерфейсом ми вважаємо те, як бібліотека експортує свої складові і розділяє їх. Припустимо, HighCharts ми в проект включаємо повністю, використовуючи лише PieChart - ось і проблема.</p>
  <p id="v5fx">Щоб пофіксити це, гарно було б розділити бібліотеку на різні модулі (плагіни): Highcharts (загальний), TableChart, PieChart, DataParsing etc. Таким чином, при оновленні лише PieChart&#x27;у, ми б майже гарантовано не отримали жодних проблем. </p>
  <p id="nqmi"><strong>Гарно</strong> реалізована з цієї точки зору бібліотека <a href="https://www.i18next.com/" target="_blank">i18next</a>. Вона поділена на плагіни, і кожний може підключатись до проекту окремо:</p>
  <pre id="K36l" data-lang="typescript">import i18next from &#x27;i18next&#x27;;
import Backend from &#x27;i18next-http-backend&#x27;;
import Cache from &#x27;i18next-localstorage-cache&#x27;;
import PostProcessor from &#x27;i18next-sprintf-postprocessor&#x27;;
import LanguageDetector from &#x27;i18next-browser-languagedetector&#x27;;

i18next
    .use(Backend)
    .use(Cache)
    .use(LanguageDetector)
    .use(PostProcessor);</pre>
  <p id="GCzv">Цей підхід також дозволяє:</p>
  <ul id="ayyu">
    <li id="b1T4">Окремо тестувати модулі розробникам бібліотеки;</li>
    <li id="Ka1S">Не включати до проекту зайвого і економити розмір бандлу;</li>
    <li id="1t5d">Винести розробникам ці модулі в різні репозиторії і працювати над ними окремо.</li>
  </ul>
  <p id="CKzN">Всі ми знаємо приклад, що <strong>суперечить</strong> принципу розділення інтерфейсів - бібліотека <a href="https://momentjs.com/" target="_blank">moment.js</a>. Вона настільки розрослась, що використовувати і сапортити її стало проблематично, тож багато проектів перейшло на аналоги, а її розробники оголосили про закінчення підтримки.</p>
  <h3 id="N4AR">Примітка</h3>
  <p id="CHTV">Варто зазначити, що інтерфейси практично завжди потрібні <strong>клієнтам</strong>, а не тому, хто надає сервіс - саме клієнти найкраще знають, що їм потрібно. Тому і створюючи інтерфейс, треба думати як клієнт, або навіть краще <strong>спитати</strong>, що клієнту треба - як у випадку з апішкою між фронтендом і бекендом.</p>
  <p id="qNaJ"></p>
  <h2 id="Z55H">Dependency Inversion Principle</h2>
  <p id="azc6">Найскладніший особисто для мене принцип.</p>
  <p id="UMhp"><strong>Формулювання</strong>:</p>
  <blockquote id="gM7W">Модулі <strong>вищого</strong> рівня не повинні залежати від модулів <strong>нижчого</strong> рівня. <strong>Всі</strong> модулі повинні залежати від <strong>абстракцій</strong>.<br /><strong>Абстракції</strong> не повинні залежати від <strong>деталей реалізації</strong>. <strong>Деталі реалізації</strong> повинні залежати від <strong>абстракцій</strong>.</blockquote>
  <p id="8hb3">Деякі розробники просто описують цей принцип як &quot;використовуйте <strong>всі класи </strong>через <strong>інтерфейси</strong>&quot;, щоб уникнути купи проблем у майбутньому, як ось тут:</p>
  <pre id="K3Nq" data-lang="typescript">interface ILogger { 
    write(message: string): void; 
} 

class FileLogger implements ILogger { 
    write(message: string): void { 
        // write to file 
    }
} 

class StandardOutLogger implements ILogger { 
    write(message: string): void { 
        // write to standard out 
    }
}

function doStuff(logger: ILogger): void { 
    logger.write(&quot;some message&quot;) 
}</pre>
  <p id="d4i4">Проте це не зовсім повне трактування, і далі я хотів би його розширити.</p>
  <p id="mt2v">По-перше: що таке модулі вищого і нижчого рівня? Нижчий рівень - це ті загальні допоміжні модулі, які використовуються у більш конкретному коді. Як приклад можна навести будь-яку зовнішню бібліотеку, власний сервіс/модуль для роботи з чимось, типу локал стореджа, або навіть якийсь сервіс, що ми використовуємо через його АПІшку. Модулі вищого рівня мають цей код як свою депенденсі. Надалі будемо розглядати саме приклад для роботи зі стореджом.</p>
  <p id="1W2w">Для чого потрібний цей принцип? Ну використовуємо ми, наприклад, локал сторедж по всьому коду, то й що? Ну, наприклад, даних стане настільки багато, що ми впремося у 5 мегабайт. Або треба зберігати картинки. Або взагалі сказали перевикористати код в нативній аплікушці, де локал стореджу просто не існує. Та це ще дрібниці - що як треба зробити мок цієї депенденсі для тестів? Всі такі зміни виливаються у купу змін в коді, через що він гарантовано десь зламається, та і ми задовбемось все переписувати.</p>
  <p id="IpUe">Але ж як зробити так, щоб не <strong>наш</strong> <strong>код</strong> залежав від бібліотеки, а <strong>бібліотека</strong> залежала від нашого коду? Ну, повернемося до <strong>другої</strong> частини <strong>першого</strong> твердження: всі модулі мають залежати від абстракцій. Окей, зробимо абстракцію над функціями роботи з локал стореджем (або бібліотекою), і будемо залежати вже від неї. Це вже покращить наше становище - АПІшка локал стореджу буде використовуватись лише в абстракції, і при її зміні не потрібно буде переписувати півпроекту. Також це запобігає <strong>сильному звʼязуванню</strong> модулів - high coupling. Проте, саме на цьому багато розробників і зупиняються - робота зроблена - і таке рішення справді підходить для маленьких проектів.</p>
  <figure id="hiL5" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/b9/1e/b91e727d-2c98-4cdb-bbc2-f7f72680051f.png" width="3164" />
    <figcaption>Приклад залежності клієнтів від абстракції над модулем локал стореджу</figcaption>
  </figure>
  <p id="x5yb">Але ж поки ми виконали лише першу частину першого твердження - абстракція все ще залежить від деталей реалізації і локал сторедж аж ніяк не залежить від абстракції. Тут маємо до абстракції додати <strong>клієнтський інтерфейс</strong> і <strong>адаптер</strong> до нього. Наприклад, будемо використовувати сторедж для роботи з АРІ токеном:</p>
  <pre id="9pDE" data-lang="typescript">interface TokenStorage {
    setToken(token: string): Promise&lt;void&gt;;
    getToken(): Promise&lt;string&gt;;
}</pre>
  <p id="pox6">Також маємо таку абстракцію:</p>
  <pre id="VsE7" data-lang="typescript">let storage;

async function setItem(name, value) {
    if (typeof storage.setItem !== &quot;function&quot;) {
        throw &quot;Storage should implement setItem method&quot;;
    }
    storage.setItem(name, value);
}

async function getItem(name) {
    if (typeof storage.getItem !== &quot;function&quot;) {
        throw &quot;Storage must implement getItem method&quot;;
    }
    return storage.getItem(name);
}

function setStorage(instance) {
    storage = instance;
}

export default {
    setStorage,
    getItem,
    setItem
};</pre>
  <p id="QlTn">і адаптер:</p>
  <pre id="pmJc" data-lang="typescript">import storage from &quot;../storage&quot;;

async function setToken(token: string): Promise&lt;void&gt; {
    storage.setItem(&quot;token&quot;, token);
}

async function getToken(): Promise&lt;string&gt; {
    return storage.getItem(&quot;token&quot;);
}

export default {
    setToken,
    getToken
};</pre>
  <p id="xETe">Отже, маємо локал сторедж, який вже тепер залежить від створеної абстракції, і надає для неї адаптер. А клієнт має інтерфейс і також залежить від абстракції.</p>
  <figure id="CauW" class="m_column" data-caption-align="center">
    <img src="https://img2.teletype.in/files/d4/26/d426e29f-eafc-4c66-9cdc-a138e3173886.png" width="2958" />
    <figcaption>Тепер абстракція залежить від деталей реалізації, а цей звʼязок полягає у звʼязці інтерфейса і адаптера</figcaption>
  </figure>
  <p id="Jrn2">Перевага цього підходу полягає у тому, що тепер після оновлення бібліотеки для роботи зі стореджом (або її заміни на іншу), клієнтський код і абстракція взагалі цього не відчують, а от якщо зміниться код в клієнті, то достатньо буде змінити його інтерфейс і відповідний адаптер.</p>
  <p id="0np0">Ось тут добре написано про ці адаптери: <a href="https://bespoyasov.ru/blog/clean-architecture-on-frontend" target="_blank">https://bespoyasov.ru/blog/clean-architecture-on-frontend</a></p>
  <p id="X8NO"></p>
  <h2 id="Ult8">Висновки</h2>
  <blockquote id="4jx4">S - Single Responsibility Principle: програмні сутності мають мати лише одну відповідальність і ця відповідальність має бути інкапсульована в них. Знижує імовірність дублювання коду. Запобігає <strong>слабкому звʼязуванню</strong> сутностей всередині модулю (<em>low cohesion</em>).</blockquote>
  <blockquote id="jf4L">O - Open-Closed Principle: програмні сутності мають бути закриті для змін і відкриті для розширення. В рази зменшує імовірність виникнення нових багів.</blockquote>
  <blockquote id="2L7n">L - Liskov Substitution Principle: методи/функції, що використовують базовий тип, повинні мати можливість використання підтипів базового типу, не знаючи про це. Тобто підклас не має вимагати від викликаючого його коду <strong>більше</strong>, ніж базовий клас, і надавати <strong>менше</strong>, ніж базовий клас. Позбавляє розробників від неочікуваних результатів роботи.</blockquote>
  <blockquote id="ZDZx">I - Interface Segregation Principle: клієнти не мають залежати від методів, які вони не використовують. Тобто якщо один інтерфейс реалізують два клієнти, але деякі  його методи не використовуються в них, треба розбити інтерфейс на два.</blockquote>
  <blockquote id="PePQ">D - Depencency Inversion Principle: модулі <strong>вищого</strong> рівня не повинні залежати від модулів <strong>нижчого</strong> рівня - <strong>всі</strong> модулі повинні залежати від <strong>абстракцій;</strong><br /><strong>Абстракції</strong> не повинні залежати від <strong>деталей реалізації - деталі реалізації</strong> повинні залежати від <strong>абстракцій</strong>. Тобто перед використанням якоїсь залежності нижчого рівня варто принаймні зробити інтерфейс/абстракцію над нею, а краще - ще й адаптер. Запобігає <strong>сильному звʼязуванню</strong> (<em>high coupling</em>) модулів.</blockquote>
  <p id="e3qL"></p>
  <h2 id="J3L9">Джерела</h2>
  <p id="aE8C">SOLID by Sergey Nemchinskiy: <a href="https://www.youtube.com/watch?v=O4uhPCEDzSo&list=PLmqFxxywkatQNWLG1IZYUhKoQrnuZHqaK" target="_blank">https://www.youtube.com/watch?v=O4uhPCEDzSo&amp;list=PLmqFxxywkatQNWLG1IZYUhKoQrnuZHqaK</a></p>
  <p id="G7OO">SOLID in FrontEnd by IT Sin9k: <a href="https://www.youtube.com/watch?v=WMO9aarO390&list=PLz_dGYmQRrr8rWKkoB3BtxF7JpCzUKny_" target="_blank">https://www.youtube.com/watch?v=WMO9aarO390&amp;list=PLz_dGYmQRrr8rWKkoB3BtxF7JpCzUKny_</a></p>
  <p id="DdxK">DIP by Web Dev Simplified: <a href="https://www.youtube.com/watch?v=9oHY5TllWaU" target="_blank">https://www.youtube.com/watch?v=9oHY5TllWaU</a></p>
  <p id="C36y">SOLID by Ostap Chervak &amp; Andrii Zhydkov: <a href="https://uncomment.fm/episodes/03_solid" target="_blank">https://uncomment.fm/episodes/03_solid</a></p>
  <p id="1dk8">SOLID by StackOverflow: <a href="https://stackoverflow.blog/2021/11/01/why-solid-principles-are-still-the-foundation-for-modern-software-architecture/" target="_blank">https://stackoverflow.blog/2021/11/01/why-solid-principles-are-still-the-foundation-for-modern-software-architecture/</a></p>
  <p id="Ss6v">SOLID book: <a href="https://ota-solid.vercel.app/" target="_blank">https://ota-solid.vercel.app/</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ohyr.dev/js-event-loop</guid><link>https://blog.ohyr.dev/js-event-loop?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/js-event-loop?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>Event Loop в JS</title><pubDate>Tue, 12 Jan 2021 15:16:56 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/12/ca/12ca4ab5-34aa-4225-9c21-febc68a56400.png"></media:content><category>JS</category><description><![CDATA[<img src="https://teletype.in/files/8f/1f/8f1fb22d-406b-4d69-8fd1-5664a9eab62f.png"></img>Як ми знаємо, двигун JS (V8) працює в одному потоці, який називається Main Thread. Щоб правильно організувати таку роботу, і щоб нічого не висло, існує Event Loop.]]></description><content:encoded><![CDATA[
  <p id="gSTD">Як ми знаємо, двигун JS (<strong>V8</strong>) працює в <strong>одному потоці</strong>, який називається <strong>Main Thread</strong>. Щоб правильно організувати таку роботу, і щоб нічого <strong>не висло</strong>, існує Event Loop.</p>
  <p id="Dak6"><strong>Event Loop,</strong> або ж цикл подій - це те, на чому заснований потік виконання в JS - всього лиш нескінченний<strong> цикл</strong>, який виконує <strong>задачі</strong>, що до нього приходять. </p>
  <p id="wXJ9">Перед тим, як приступити до будь-яких черг, треба класифікувати задачі, які потрапляють до Event Loop. Отже, є три типи задач:</p>
  <ul id="plH5">
    <li id="pwfL">Macrotask (макрозадача) - також називають просто Task;</li>
    <li id="xWTv">Microtask (мікрозадача);</li>
    <li id="Gxrw">Render Task.</li>
  </ul>
  <p id="Mi2K">А тепер детальніше:</p>
  <ul id="h3MQ">
    <li id="4RWt"><strong>Макротаски </strong>- це <strong>синхронний код</strong>, підвантаження тегів<code>&lt;script&gt;</code>, обробка подій, а також частина Browser API: <code>setInterval</code>, <code>setTimeout</code> (точніше, функції, що в них передаються)... До цього типу задач відносяться майже всі, тому якщо сумніваємось - вважаємо, що це макрозадача.</li>
    <li id="5Rgt"><strong>Мікротаски </strong>- в основному <strong>проміси</strong>, а точніше функції, які виконуються в <code>then</code>, <code>catch</code>, <code>finally</code>, а також з використанням <code>await</code>, тому що це теж обробка проміса.</li>
    <li id="wxiZ"><strong>Задачі рендера</strong> - все, що стосується <strong>рендеру сторінки</strong>: <code>Style Recalculate</code>, <code>Layout</code>, <code>Paint</code>...</li>
  </ul>
  <p id="fE0e">Для кожного з цих 3 типів задач існує своя <strong>черга</strong>: <code>Macrotasks queue</code>, <code>Microtasks queue</code> та <code>Render queue</code>. Завдання в ці черги потрапляють відразу ж, як вони зустрічаються в коді (ну чи в коді іншого файлу - коли ми курсор пересунули, наприклад), але це в жодному разі не означає, що вони виконуються миттєво. Ці черги якраз створені, щоб <strong>імітувати багатопоковість</strong>.</p>
  <p id="5cFJ">Тепер можемо подивитись на схему:</p>
  <figure id="KQVg" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/8f/1f/8f1fb22d-406b-4d69-8fd1-5664a9eab62f.png" width="1442" />
    <figcaption>Різні черги і один Event Loop</figcaption>
  </figure>
  <p id="SVny">Як бачимо, є 3 різні черги і один нескінченний цикл, до якого потрапляють задачі з цих черг. А <strong>Event Loop</strong> власне просто перекидає в Call Stack те, що потрапляє до нього.</p>
  <p id="NHNF">Виходить, вся багатопотоковість тут у тому, щоб правильно <strong>розставити пріоритети задач</strong> у Event Loop&#x27;і. Це зараз і розглянемо.</p>
  <p id="Q9pK">Але спочатку фрагмент коду. В якому порядку стрічки будуть виведені в консоль?</p>
  <pre id="cnVf" data-lang="javascript">setTimeout(() =&gt; console.log(&quot;timeout&quot;), 0);

Promise.resolve().then(() =&gt; console.log(&quot;promise&quot;));

console.log(&quot;code&quot;);</pre>
  <p id="s3S5"></p>
  <p id="lEZb">Цю задачу дуже люблять давати на співбесідах. Поки ви думаєте, розпишу загальний алгоритм роботи Event Loop&#x27;а:</p>
  <ol id="tzb1">
    <li id="9cjH">Виконуємо <strong>одну </strong>макротаску;</li>
    <li id="8O1x">Виконуємо послідовно <strong>всі </strong>мікротаски;</li>
    <li id="rIKJ">Виконуємо послідовно <strong>всі </strong>задачі рендеру (цей етап може бути оптимізований браузером, якщо він вирішить, що рендер на конкретній ітерації не потрібен).</li>
  </ol>
  <p id="UVzh">І так <strong>по колу</strong>. Звичайно, оскільки всі задачі лежать у чергах, спочатку будуть виконані старші, а потім нові.</p>
  <p id="jOxF">Теперь повернемось до фрагменту коду. Ось відповідь:</p>
  <pre id="HsjY" data-lang="javascript">&quot;code&quot;
&quot;promise&quot;
&quot;timeout&quot;</pre>
  <p id="VAzS">Давайте розберемось, чому так. Спочатку V8 <strong>проходиться по всьому коду</strong> (виконує його як синхронний). Цей етап можна назвати <strong>макрозадачею</strong>, оскільки ця задача по суті - виконання коду в тезі <code>&lt;script&gt;</code>. А теперь давайте подивимось, що ми робимо, поки виконуємо цю макрозадачу:</p>
  <ol id="t4xO">
    <li id="j6nX"><code>setTimeout(() =&gt; console.log(&quot;timeout&quot;), 0);</code> - ми передаємо у функцію з Browser API свою функцію і хочемо, щоб вона виконалась <strong>одразу ж</strong>. Ну браузер так і робить - одразу ж додає її в чергу - чергу <strong>макрозадач</strong>.</li>
    <li id="nz1e"><code>Promise.resolve().then(() =&gt; console.log(&quot;promise&quot;));</code> - тут ми одразу ж резолвимо проміс и хочемо, щоб далі виконалась передана функция. Як і в попередньому випадку, вона повинна виконатись &quot;<strong>миттєво</strong>&quot;. Але це вже буде <strong>мікрозадача</strong>, тому що це виконання функції в <code>then()</code>. Додаємо її у чергу мікрозадач.</li>
    <li id="dkKo"><code>console.log(&quot;code&quot;);</code> - а це звичайний <strong>синхронний</strong> код.</li>
  </ol>
  <p id="oARw">Виходить, що функції з перших двох виразів потрапили до відповідних черг. А ось третій вираз виконується хоч і після перших двох, але в контексті <strong>першої макрозадачі</strong> - виконання скрипту. Саме тому рядок <code>&quot;code&quot;</code> виведеться першим.</p>
  <p id="Wp6o">Тепер наш код виконався, і перша макрозадача виконана. Виходить, що Event Loop виконав одну макрозадачу, і далі за алгоритмом слід виконувати всі <strong>мікрозадачі</strong>. Мікрозадача у нас лише одна в черзі - виконання колбека промісу. Виконуємо її та виводимо в консоль <code>&quot;promise&quot;</code>. </p>
  <p id="mURo">Далі за алгоритмом слідує виконання всіх задач рендеру, але у нас їх немає, тому пропускаємо. Ну і нарешті – знову виконуємо одну <strong>макрозадачу</strong>. У черзі макрозадач лежить лише колбек <code>setTimeout&#x27;у</code>, виконуємо його і виводимо в консоль <code>&quot;timeout&quot;</code>.</p>
  <p id="wBaO"></p>
  <h2 id="R9ZQ">Навмисне створення макрозадач</h2>
  <p id="1oTq">Інколи буває корисно <strong>навмисне </strong>додати в чергу макрозадач яку-небудь функцію. Ось приклад:</p>
  <pre id="fwg7" data-lang="javascript">&lt;div id=&quot;progress&quot;&gt;&lt;/div&gt; 

&lt;script&gt;
    function count() { 
        for (let i = 0; i &lt; 1e9; i++) {
            i++; 
            progress.innerHTML = i; 
        } 
    } 
    
    count(); 
&lt;/script&gt;</pre>
  <p id="721E">Тут ми імітуємо <strong>складні обчислення</strong> – рахуємо до мільярда. Неважко здогадатися, що якщо виконати цей фрагмент коду, вкладка браузера зависне на якийсь час - не реагуватиме на <strong>жодні дії </strong>користувача. Вся справа в тім, що це одна велика <strong>макрозадача</strong>. І поки вона не виконається – ніщо інше не може виконуватися.</p>
  <p id="vekN">Насправді бувають приклади дійсно складних операцій. І щоб під час їх виконання браузер не зависав (або щоб мати можливість показати прогрес-бар того, що ми робимо), користуються лайфхаками для <strong>поділу</strong> цих завдань на <strong>дрібніші</strong>. Давайте так і зробимо:</p>
  <pre id="l3il" data-lang="javascript">&lt;div id=&quot;progress&quot;&gt;&lt;/div&gt;

&lt;script&gt; 
    let i = 0;
    const count = () =&gt; { 
        // виконати частину великої задачі
        do { 
            i++;
            progress.innerHTML = i; 
        } while (i % 1e6 != 0); 
        
        if (i &lt; 1e9 - 1e6) { 
            setTimeout(count); 
        }
    } 
    
    count(); 
&lt;/script&gt;</pre>
  <p id="OI8j">Тепер цикл рахує до мільйона замість мільярда. Але після циклу є <strong>умова</strong>: якщо ми дорахували до мільярда – завершити виконання, а якщо ні – запустити цю ж функцію <strong>знову</strong>, але вже через <code>setTimeout</code>. Саме це і є лайфхак із розбиттям <strong>макрозадачі</strong> на кілька шматків. Тепер сторінка зможе реагувати на наші дії, бо ми <strong>розділили </strong>одну макрозадачу на тисячу дрібніших, а також ми зможемо бачити зміни в DOM.</p>
  <p id="DHlt">Як ми пам&#x27;ятаємо, після виконання <strong>однієї</strong> макрозадачі, виконуються всі мікрозадачі та задачі рендеру, а потім знову одна макрозадача, і так нескінченно. Так ось, якщо ми під час виконання першої макрозадачі, наприклад, клікнули на кнопку, вже ця макрозадача піде в чергу і буде виконана <strong>перед</strong> тим, як розпочати підрахунок наступного мільйона.</p>
  <p id="yc3Y">Насправді, цей фрагмент коду можна зробити ще кращим:</p>
  <pre id="pj2D" data-lang="javascript">&lt;div id=&quot;progress&quot;&gt;&lt;/div&gt;

&lt;script&gt; 
    let i = 0;
    const count = () =&gt; { 
        if (i &lt; 1e9 - 1e6) { 
            setTimeout(count); 
        }
        
        // виконати частину великої задачі
        do { 
            i++;
            progress.innerHTML = i; 
        } while (i % 1e6 != 0);
    } 
    
    count(); 
&lt;/script&gt;</pre>
  <p id="cvhS">Все, що ми змінили - перенесли <code>setTimeout</code> <strong>на початок функції </strong>-це дасть нам можливість зекономити десь <strong>4мс</strong> на <strong>кожному </strong>її виклику. Чому? Бо навіть якщо ми передали в <code>setTimeout</code> значення <strong>0</strong>, тобто <strong>нульова</strong> затримка, все одно перед додаванням колбека до черги макрозадач мине близько 4мс. А так ми їх не втрачаємо і працюємо в цей час, коли в попередньому прикладі нам довелось би чекати.</p>
  <p id="mmTI">Такий лайфхак часто використовується при маніпуляціях з <strong>фокусом</strong> - щоб напевне поставити його туди, куди треба нам, при цьому уникнувши проблем (наприклад, якщо за дефолтом він стрибає на <strong>інший</strong> елемент).</p>
  <p id="jdEK"></p>
  <h2 id="PEYt">Навмисне створення мікрозадач</h2>
  <p id="0x20">А ось наступний <strong>лафхак </strong>цілком &quot;легітимний&quot; і потрібний для того, щоб запустити функцію <strong>асинхронно </strong>(після поточного коду), але <strong>до ререндеру</strong> (бо мікрозадачі виконуються до рендеру). Для цього достатньо скористатись вбудованою функцією <code>queueMicrotask(func)</code>, яка одразу ж додасть нашу функцію до черги <strong>мікрозадач</strong>:</p>
  <pre id="pvQI" data-lang="javascript">&lt;div id=&quot;progress&quot;&gt;&lt;/div&gt;

&lt;script&gt; 
    let i = 0;
    const count = () =&gt; { 
        if (i &lt; 1e9 - 1e6) { 
            queueMicrotask(count);
        }
        
        // виконати частину великої задачі
        do { 
            i++;
            progress.innerHTML = i; 
        } while (i % 1e6 != 0);
    } 
    
    count(); 
&lt;/script&gt;</pre>
  <p id="agKv">Використавши <code>queueMicrotask(func)</code>, ми отримали такий самий результат, як при виконанні <strong>синхронного</strong> <strong>коду</strong> з першого фрагменту - результат зʼявляється в DOM лише <strong>після</strong> завершення обчислень.</p>
  <p id="nvzL">Нащо ж тоді це треба? Ну, насправді, тут вже кейси доволі специфічні. Детальніше можете ознайомитись ось тут: <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide" target="_blank">https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide</a>, особисто мені сподобалась секція <strong>Batching Operations</strong>.</p>
  <p id="xd7y"></p>
  <h2 id="IGDL">Кілька слів про Web Workers</h2>
  <p id="KP2l">Для тривалих важких обчислень, які не повинні <strong>блокувати цикл подій</strong>, ми можемо використовувати Web Workers. Це спосіб виконання коду в іншому, <strong>паралельному</strong> потоці. </p>
  <p id="APE9">Web Workers можуть обмінюватись повідомленнями с основним процесом, але они мають свої змінні і <strong>свой цикл подій</strong>. При цьому вони не мають доступу до DOM, тому основне їх застосування – <strong>обрахунки</strong>. Вони дозволяють використовувати кілька ядер процесора одночасно.</p>
  <p id="NVy7"></p>
  <h2 id="lvzi">Висновки</h2>
  <blockquote id="Jfbn">Двигун JS - V8 - працює в <strong>одному </strong>потоці, який має назву Main Thread.</blockquote>
  <blockquote id="7tEq">Event Loop - це <strong>нескінченний цикл</strong>, який є основою потоку виконання в JS, і який працює з <strong>задачами</strong>.</blockquote>
  <blockquote id="ifbG">Щоб забезпечити імітацію багатопотоковості, задачі в JS поділяються на кілька типів: <strong>макрозадачі</strong>, <strong>мікрозадачі</strong> і <strong>задачі рендеру</strong>. Для кожного типу задач існує одноіменна <strong>черга</strong>.</blockquote>
  <blockquote id="v6aQ">Макрозадачі - основний тип задач - це <strong>синхронні </strong>задачі. До них входять виконання тегів <code>&lt;script&gt;</code> - всього коду, виконання <strong>обробників подій</strong>, а також частина <strong>Browser API</strong>: <code>setTimeout</code>, <code>setInterval</code>...</blockquote>
  <blockquote id="VAZs">Мікрозадачі - це <strong>асинхронні </strong>задачі. До них входять виконання колбеків промісів (<code>then</code>, <code>catch</code>, <code>finally</code>), а також функції, які виконуються через<code>await</code>.</blockquote>
  <blockquote id="0e2z">Задачі рендеру - це все, що стосується <strong>ререндеру</strong> сторінки.</blockquote>
  <blockquote id="grsc">Алгоритм роботи Event Loop&#x27;a такой:<br />1. Виконуємо <strong>одну макрозадачу</strong> з черги макрозадач;<br />2. Виконуємо <strong>всі мікрозадачі</strong> з черги мікрозадач;<br />3. Виконуємо <strong>всі задачі рендеру</strong> з черги задач рендеру.</blockquote>
  <blockquote id="G3z9">Для <strong>поділу </strong>важких обчислень користуємось <code>setTimeout&#x27;ом</code> з нульовою затримкою, щоб дати можливість <strong>у перерві</strong> виконатись усім задачам з черг макрозадач, мікрозадач и задач рендеру.</blockquote>
  <blockquote id="n2b8">Якщо хочемо виконати функцію <strong>асинхронно </strong>(після поточного коду), але <strong>до рендеру</strong> і обробки нових подій - користуємось функцією <code>queueMicrotask</code>. Це потрібно у специфічних випадках, як-от правильна обробка <strong>батчів</strong> або <strong>кешування</strong>.</blockquote>
  <blockquote id="SLJU">Для важких обчислень зазвичай використовують HTML Web Workers, які мають <strong>свій цикл подій</strong> і виконують код у паралельному потоці, що дозволяє не блокувати основний.</blockquote>
  <p id="SVGG"></p>
  <h2 id="KIv7">Джерела</h2>
  <p id="nLkn"><a href="https://learn.javascript.ru/event-loop" target="_blank">https://learn.javascript.ru/event-loop</a></p>
  <p id="9vj3"><a href="https://habr.com/ru/post/461401/" target="_blank">https://habr.com/ru/post/461401/</a></p>
  <p id="AaIX"><a href="https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript" target="_blank">https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ohyr.dev/js-context</guid><link>https://blog.ohyr.dev/js-context?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/js-context?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>Контекст в JS</title><pubDate>Tue, 22 Dec 2020 17:24:32 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/41/27/412737f6-d2b2-4611-a4b4-aec67e1330a7.png"></media:content><category>JS</category><description><![CDATA[<img src="https://img4.teletype.in/files/7e/9f/7e9fc47f-eb6d-473c-b7d6-49548f11227b.png"></img>Контекст (зазвичай називають просто this) - ключове слово, яке вказує на контекст виконання функції - обʼєкт. По суті це об'єкт, на якому викликана функція. Але важливо уточнити, що:]]></description><content:encoded><![CDATA[
  <figure id="pd8l" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/7e/9f/7e9fc47f-eb6d-473c-b7d6-49548f11227b.png" width="1058" />
    <figcaption>З this не просто так роблять меми</figcaption>
  </figure>
  <p id="5znz">Контекст (зазвичай називають просто <strong>this</strong>) - <strong>ключове слово</strong>, яке вказує на контекст виконання функції - обʼєкт. По суті це об&#x27;єкт, на якому викликана функція. Але важливо уточнити, що:</p>
  <ul id="9sU5">
    <li id="UdEx">По-перше, значення, яке повертає це ключове слово, <strong>не статичне</strong>, як у інших мовах програмування, тобто може змінюватись (але самі ми його банально присвоїти не можемо);</li>
    <li id="AA22">По-друге, цей обʼєкт визначається <strong>в момент виклику</strong> функції і посилання на нього повертається при використанні ключового слова <code>this</code>.</li>
  </ul>
  <p id="YVhv">Простіше буде уявити цей <code>this</code> як ключове слово, яке повертає значення якогось <strong>секретного поля</strong> в функції, в якому зберігається посилання на об&#x27;єкт, в якому виконується функція - контекст. (<em>можливо, це так і є, я просто ще не знаю</em>).</p>
  <p id="z1Dv">Одразу ж питання в студію: що буде виведено тут?</p>
  <pre id="BilF" data-lang="javascript">const myFunction = function() {
    console.log(this);
}

myFunction();</pre>
  <p id="lL1k">Як ми вже знаємо, контекст - це об&#x27;єкт, у якому працює функція. Ми <strong>викликаємо</strong> її там, де й створили, тож тут відповідь – <code>Window</code>.</p>
  <blockquote id="iBPL">Насправді, не завжди. Зараз досить часто використовується <code>strict mode</code>, а якщо він увімкнений, то глобальний контекст буде посилатися вже не на <code>Window</code>, а на <code>undefined</code>. Це варто запам&#x27;ятати, бо строгий режим увімкнено за замовчуванням вже майже скрізь.</blockquote>
  <p id="oK44">Тепер переходимо до <strong>обʼєктів </strong>- саме з ними контекст і використовується.</p>
  <p id="fUq4">Чого чекати в консолі, якщо викликати цю функцію?</p>
  <pre id="jFSY" data-lang="javascript">const myObject = {
    myMethod: function() {
        console.log(this);
    }
};</pre>
  <p id="HPdX">Насправді, тут питання не &quot;чого?&quot;, а &quot;як?&quot;. А саме, все буде залежати від того, <strong>як </strong>ми <strong>викличемо </strong>цю функцію. Ось зібрав кілька варіантів:</p>
  <pre id="1wku" data-lang="javascript">//1
myObject.myMethod();

//2
const func = myObject.myMethod;
funct();</pre>
  <p id="W7RG">В першому варіанті ми, очевидно, отримаємо посилання на сам обʼєкт <code>myObject</code>, тому що функція була викликана <strong>на ньому</strong>. А ось у другому прикладі ми &quot;витягуємо&quot; функцію з обʼєкта і викликаємо її вже <strong>без привʼязки</strong> до нього. У такому випадку ми отримаємо <code>Window</code> або <code>undefined</code>, тому що визначення контексту відбувається <strong>при виклику</strong> функції, а не при створенні.</p>
  <p id="baLX">Звісно, можемо провернути і такой трюк:</p>
  <pre id="7htR" data-lang="javascript">const myMethod = function() {
    console.log(this);
};

const myObject = {
    myMethod: myMethod
};

myObject.myMethod(); // myObject</pre>
  <p id="Sky4">Тут ми вже навпаки &quot;помістили&quot; свою функцію до обʼєкту. Але ж від цього нічого не змінилось - ми все одно отримаємо сам обʼєкт у якості <code>this</code>, бо викликаємо функцію на обʼєкті.</p>
  <blockquote id="B5PA">Те, що ми щойно зробили, називається <strong>implicit binding,</strong> тобто неявне звʼязування. У цьому випадку контекст визначається сам, без нашої участі. Але є ще <strong>explicit binding і hard binding.</strong></blockquote>
  <p id="64XZ"> </p>
  <h2 id="nBaM">Explicit binding</h2>
  <p id="dcJT">Мb знаємо, що у всіх функцій є такі методи, як <strong>call і</strong> <strong>apply</strong>. Між собою вони майже не відрізняються, а ось їх сенс - <strong>викликати </strong>функцію з <strong>потрібним нам</strong> <strong>контекстом</strong>:</p>
  <pre id="VUaU" data-lang="javascript">const myMethod = function() {
    console.log(this);
};

const myObject = {
    myMethod: myMethod
};

myMethod(); // this === Window
myMethod.call(myObject, arg1, arg2,...); // this === myObject
myMethod.apply(myObject, [array of args]); // this === myObject</pre>
  <p id="CITf">Навіть додати нічого. Скажу тільки, що явне (<code>explicit</code>) звʼязування завжди <strong>переважає</strong> над неявним (<code>implicit</code>), тому не важливо, як ми викликаємо <code>myMethod</code> - на обʼєкті <code>myObject</code> чи як у фрагменті коду, якщо ми <strong>явно </strong>привʼязуємосвій контекст.</p>
  <p id="Rlp0"></p>
  <h2 id="ykLb">Hard binding</h2>
  <p id="iPSi">Звичайно, у функцій є ще один метод - <code>bind</code>. Його призначення відрізняється від попередніх <code>call</code> і <code>apply</code>. Цей метод використовується, щоб <strong>назавжди </strong>звʼязати функцію з наданим контекстом. Звісно, щоб не &quot;псувати&quot; початкову функцію, метод повертає нам <strong>нову функцію</strong> з уже <strong>привʼязаним </strong>контекстом:</p>
  <pre id="gnVS" data-lang="javascript">const myMethod = function() {
    console.log(this);
};

const myObject = {
    message: &quot;Hello&quot;
};

const newMethod = myMethod.bind(myObject);
newMethod(); // this === myObject</pre>
  <p id="P7Ul">Це називається <strong>hard</strong> <strong>binding,</strong> оскільки ми назавжди привʼязуємо до функції наш контекст. Варто зазначити, що тепер <strong>явне звʼязування</strong> працювати вже <strong>не буде</strong>:</p>
  <pre id="o7Hy" data-lang="javascript">const myMethod = function() {
    console.log(this.a);
};

const obj1 = {
    a: 2
};

const obj2 = {
    a: 3
};

const newMethod = myMethod.bind(obj1); // { a: 2 }
newMethod.call(obj2); // все ще { a: 2 }</pre>
  <p id="q3c8"></p>
  <h2 id="8oD6">Трохи про минуле</h2>
  <p id="Dwxl">Раніше ж класів у JS не було, тому їх роль виконували функції з ключовим словом <code>new</code>. Розглянемо поведінку контексту в такій ситуації:</p>
  <pre id="z0qX" data-lang="javascript">function foo(a) {
    this.a = a;
}

const bar = new foo(2);
console.log(bar.a) // 2</pre>
  <p id="6AwK">Як бачимо, якщо функція була викликана з ключовим словом <code>new</code>, її контекстом буде новий обʼєкт. Тобто, оскільки в будь-якому випадку створюється новий обʼєкт, нам <strong>не важливо</strong>, привʼязували ми контекст вручну чи ні:</p>
  <pre id="M0Ti" data-lang="javascript">function foo(something) {
    this.a = something;
}

const obj1 = {};

const bar = foo.bind(obj1);
bar(2);
console.log(obj1.a) // 2

const baz = new bar(3);
console.log(obj1.a) // 2
console.log(baz.a) // 3</pre>
  <p id="lxqs"></p>
  <h2 id="EOji">Використовуємо функції з контекстом</h2>
  <p id="kKsb">Насправді, контекст часто використовується саме в роботі з функціями з бібліотек. Наприклад, ми робимо запит і хочемо, щоб після нього викликався наш колбек (яким є функція). І тут, звичайно ж, починаються проблеми з контекстом:</p>
  <pre id="e0F9" data-lang="javascript">const myObject = {
    myMethod: function() {
        helperObject.doSomething(&#x27;text&#x27;, this.onDone);
    },
    
    onDone: function() {
        // Сам бог знає, який &quot;this&quot; тут буде
    }
};</pre>
  <p id="XqSH">Ви могли подумати, що контекстом у функції <code>onDone</code> буде обʼєкт, в якому вона <strong>створена </strong>- <code>myObject</code>. Але ж контекст визначається тільки тоді, коли функція <strong>викликається</strong>, тому ми і справді не можемо вгадати, яким буде контекст у цьому колбеці, коли бібліотека його викличе.</p>
  <p id="jjV0">Ця проблема має кілька рішень (і ще одне розглянемо пізніше у цій статті).</p>
  <p id="pCKS"><strong>Перше:</strong> часто бібліотеки надають ще один параметр для передачі нашого контексту для колбеку:</p>
  <pre id="3gma" data-lang="javascript">const myObject = {
    myMethod: function() {
        helperObject.doSomething(&#x27;text&#x27;, this.onDone, this);
    },
    
    onDone: function() {
        // this === myObject
    }
};</pre>
  <p id="8QGA"><strong>Друге:</strong> звичайно ж ми можемо передати колбек з уже <strong>привʼязаним </strong>контекстом:</p>
  <pre id="EUxU" data-lang="javascript">const myObject = {
    myMethod: function() {
        helperObject.doSomething(&#x27;text&#x27;, this.onDone.bind(this));
    },
    
    onDone: function() {
        // this === myObject
    }
};</pre>
  <p id="T0HA"><strong>Третє: </strong>створюємо замикання і зберігаємо <code>this</code> у ньому:</p>
  <pre id="kOHY" data-lang="javascript">const myObject = {
    myMethod: function() {
        var that = this;    

        helperObject.doSomething(&#x27;text&#x27;, function() {
            // Сам бог знає, який &quot;this&quot; тут буде, але у нас є &quot;that&quot;
        });
    }
};</pre>
  <p id="TLVp">Але так робити не рекомендується, бо це костиль. Таким чином ми починаємо користуватись якимись змінними замість <strong>справжнього контексту</strong> і можемо загубитись.</p>
  <p id="5gbf">Звісно ж, усі ці проблеми зʼявляються, коли ми використовуємо і нативні функції накшталт <code>setTimeout</code>, <code>addEventListener</code> або<code>forEach</code>... Тож невже ми маємо завжди гратись з контекстом?</p>
  <p id="UzhE"></p>
  <h2 id="3DOY">Контекст у стрілкових функціях</h2>
  <p id="4dSG">Ви вже мабуть помітили, що до цього в усіх прикладах використовувались звичайні функції, а не стрілкові. Звісно, звичайні функції все ще використовуються, але безглуздо заперечувати, що стрілкові функції більш &quot;популярні&quot;, ніж звичайні. Тому саме час зрозуміти, як у них працює контекст.</p>
  <p id="6K5E">Отже, почнемо з того, що у стрілкових функцій <strong>немає </strong>свого контексту. Кілька прикладів:</p>
  <pre id="718C" data-lang="javascript">const myFunction = () =&gt; {
    console.log(this);
};

myFunction(); // Window || undefined</pre>
  <p id="RCFv">А щось змінилось? Поки нічого. Але що скажете тут?</p>
  <pre id="HpyK" data-lang="javascript">const myObject = {
    myMethod: () =&gt; {
        console.log(this);
    }
};

myObject.myMethod(); // ???</pre>
  <p id="w5Af">А ось і відповідь. Як бачимо, поведінка нічим не відрізняється від попереднього прикладу, навіть якщо ми витягнемо функцію з обʼєкту:</p>
  <pre id="I9bX" data-lang="javascript">myObject.myMethod(); // Window || undefined

const myMethod = myObject.myMethod;
myMethod(); // Window || undefined</pre>
  <p id="jKzE">Але чому? Як я вже казав, у стрілкових функцій просто немає контексту. Але звідки ж ми його тоді беремо? Можливо, це &quot;обʼєкт на рівень вище&quot;?</p>
  <pre id="pfYY" data-lang="javascript">const parentObj = {
    childObj: {
        message: &quot;Hello&quot;,
        method: () =&gt; {
            console.log(this);
        }
    }
};

parentObj.childObj.method(); // Window || undefined</pre>
  <p id="xRS0">І знов ні... З більшою вкладеністю теж працювати не буде.</p>
  <p id="8NMR"></p>
  <h2 id="JPt0">Розуміємо контекст у стрілкових функціях</h2>
  <p id="uRzB">У більшості статей використовується поняття <strong>&quot;батьківський контекст&quot;</strong>. Забудьте його, оскільки воно може дуже легко заплутати. А нормальне пояснення - нижче.</p>
  <p id="6eA4">На початку цієї статті я казав, що <code>this</code> - всього лиш <strong>ключове слово</strong>, яке повертає <strong>посилання </strong>на контекст функции, який у свою чергу є якимось <strong>обʼєктом</strong>. Це ми говоримо про звичайні функції.</p>
  <p id="ErkK">А якщо казати про стрілкові функції, то у них просто <strong>немає контексту</strong>. Відповідно, коли ми звертаємося до <code>this</code> у стрілковій функції, ми просто &quot;<strong>проходимося по всім скоупам</strong>&quot; і &quot;<strong>знаходимо</strong>&quot; якийсь контекст вище, от і все. Якщо знаходимо - він дорівнює контексту функції (звичайної), у якій виконується наша стрілкова функція. Якщо не знаходимо - отримуємо глобальний об&#x27;єкт або <code>undefined</code>, залежно від того, чи увімкнений строгий режим, тому що весь код у будь-якому випадку десь виконується, і у цього &quot;десь&quot; є свій контекст.</p>
  <p id="9OBT"></p>
  <h2 id="LEvK">Привʼязуємо свій контекст до стрілкових функцій</h2>
  <p id="9EGl">Ну і, звісно ж, оскільки у стрілкових функцій немає контексту, новий приклеїти до них <strong>не вийде</strong>. Тобто, <code>bind</code>, <code>call</code> та <code>apply</code> ніякого ефекту не дадуть, якщо їх застосовувати до стрілкових функцій.</p>
  <p id="NE4Q">Також стрілкові функції - це вже не минуле, тому їх не можна викликати як конструктор з використанням ключового слова <code>new</code>.</p>
  <p id="A8uw"></p>
  <h2 id="QLU4">Використовуємо стрілкові функції без контексту</h2>
  <p id="v4wA">Ці функції, не маючи власного контексту, буквально <strong>створені</strong> для того, щоб бути <strong>переданими</strong> кудись. Вони створені для невеликого коду, який не має свого контексту, виконуючись у поточному. Ось приклад, в якому чудово працюють стрілкові функції:</p>
  <pre id="Ns70" data-lang="javascript">let group = {
    title: &quot;Our Group&quot;,
    students: [&quot;John&quot;, &quot;Pete&quot;, &quot;Alice&quot;],
    
    showList() {
        this.students.forEach(st =&gt; alert(&#x60;${this.title}: ${st}&#x60;));
    }
};

group.showList();</pre>
  <p id="taGC">А ось звичайна функція тут просто зламається:</p>
  <pre id="jdeV" data-lang="javascript">let group = {
    title: &quot;Our Group&quot;,
    students: [&quot;John&quot;, &quot;Pete&quot;, &quot;Alice&quot;],
    
    showList() {
        this.students.forEach(function(student) {
            // Error: Cannot read property &#x27;title&#x27; of undefined
            alert(&#x60;${this.title}: ${st}&#x60;);
        });
    }
};

group.showList();</pre>
  <p id="pKJE">Вся сппава в тім, що звичайна функція отримує <strong>свій </strong>контекст, який <strong>бог знає чому рівний</strong>. І швидш за все, поля <code>title</code> у тому контексті не буде. А у стрілкової функції <strong>немає свого</strong> <strong>контексту</strong>, тому ми шукаємо вже існуючий контекст і отримуємо посилання на обʼєкт <code>group</code>. Саме з цієї причини стрілкові функції ідеально підходять для роботи із замиканнями або у якості колбеків.</p>
  <p id="Ze3D"></p>
  <h2 id="ofdz">Як працює this</h2>
  <p id="HquK">Ну і наостанок хотів би розповісти, як насправді <strong>this </strong>дає нам посилання на обʼєкт, у якому виконується функція. Почнемо з прикладу:</p>
  <pre id="tYbd" data-lang="javascript">let user = {
    name: &quot;John&quot;,
    hi() { alert(this.name); },
    bye() { alert(&quot;Bye&quot;); }
};

user.hi(); // &quot;John&quot;
(user.name === &quot;John&quot; ? user.hi : user.bye)(); // Error!</pre>
  <p id="MOSo">Як бачимо, такі функції (методи) ще і викликати правильно треба вміти! Чому ж при другому виклиці отримуємо помилку?</p>
  <p id="L5zm">Якщо уважно подивитись, виклик складається з двох операторів: оператора-крапки та круглих дужок. Якщо з круглими дужками все зрозуміло, то із крапкою потрібно розібратися.</p>
  <p id="ec1V">Ми казали, що те, що нам поверне <code>this</code>, залежить від того, як ми викличемо функцію. Тому справа тут якраз у <strong>операторі-крапці</strong> – саме він передає інформацію про об&#x27;єкт далі, до круглих дужок.</p>
  <p id="G0py">Так ось насправді оператор-крапка повертає не саму функцію, а деяке <strong>спеціальне</strong> значення, зване <strong>Reference Type</strong>. Цей тип є внутрішнім, і містить три значення: <code>base</code>, <code>name</code>, <code>strict</code>. Як ви вже здогадалися, в <code>base</code> лежить посилання на обʼєкт, <code>name</code> - це ім&#x27;я поля, а <code>strict</code> - чи увімкнений строгий режим. Тобто коли ми робимо виклик <code>user.hi()</code>, до круглих дужок доходить таке значення: <code>(user, &quot;hi&quot;, true)</code>. І коли дужки <strong>застосовуються</strong> до такого значення (відбувається виклик), вони отримують повну інформацію про об&#x27;єкт, і можуть визначити правильний <code>this</code>.</p>
  <p id="5Alr">Що відбувається далі, на наступному рядку? Справа в тім, що значення <strong>Reference Type</strong> – це <strong>не звичайне посилання</strong>. А коли ми застосовуємо тернарний оператор (або будь-який інший оператор) до значень <strong>Reference Type</strong>, цей Reference Type змушений <strong>конвертуватися у звичайне значення</strong>, у даному випадку - функцію. І коли ми доходимо до круглих дужок - вони отримують лише функцію, а не значення Reference Type, що і призводить до <strong>втрати контексту</strong>. Ось ще один приклад для закріплення:</p>
  <pre id="2d33" data-lang="javascript">let obj, method;

obj = {
    go: function() { alert(this); }
};

obj.go();                  // (1) [object Object]

(obj.go)();                // (2) [object Object]

(method = obj.go)();       // (3) undefined

(obj.go || obj.stop)();    // (4) undefined</pre>
  <p id="uogR"></p>
  <h2 id="PG0b">Висновки</h2>
  <blockquote id="f2lN">Контекст - це обʼєкт, в контексті якого працює функція (обʼєкт, на якому викликана функція).</blockquote>
  <blockquote id="OoUW"><code>this</code> - ключове слово, яке <strong>при виконанні</strong> повертає посилання на контекст.</blockquote>
  <blockquote id="jop6">Контекст залежить від того, як викликана функція, і визначається лише <strong>при її виконанні</strong>.</blockquote>
  <blockquote id="VuoP">Глобальний контекст рівний обʼєкту <code>Window</code> або <code>undefined</code>, в залежності від того, чи увімкнений строгий режим.</blockquote>
  <blockquote id="5mN3">Implicit binding - це <strong>неявне</strong> визначення контексту при виклику функції.</blockquote>
  <blockquote id="fXEz">Explicit binding - це <strong>явне</strong> <strong>звʼязування</strong> контексту з функцією з використанням <code>call</code> и <code>apply</code>. <code>call</code> від <code>apply</code> відрізняється лише тим, що <code>call</code> приймає <strong>список послідовних аргументів</strong>, а <code>apply</code> - <strong>масив</strong> з усіма аргументами.</blockquote>
  <blockquote id="ZN60">Hard binding - це <strong>жорстке звʼязування</strong> контексту і функції з використанням <code>bind</code>. Функція з контекстом, що був привʼязаний у такий спосіб, завжди буде посилатися <strong>лише на цей контекст</strong>, і жоден <code>call</code> чи <code>apply</code> не зможуть це змінити.</blockquote>
  <blockquote id="8Yrb">У cтрілкових функцій <strong>немає</strong> свого контексту, тому при зверненні в них до <code>this</code> шукається і повертається <strong>найближчий існуючий контекст</strong>. <code>call</code>, <code>apply</code> и <code>bind</code> <strong>не працюють</strong> зі стрілковими функціями.</blockquote>
  <blockquote id="8otI">Стрілкові функції <strong>створені</strong> для того, щоб бути <strong>переданими</strong> в інший код у якості <strong>колбеку</strong>.</blockquote>
  <p id="PkEQ"></p>
  <h2 id="mC3p">Джерела</h2>
  <p id="jSpO"><a href="https://www.codementor.io/@dariogarciamoya/understanding--this--in-javascript-du1084lyn" target="_blank">https://www.codementor.io/@dariogarciamoya/understanding--this--in-javascript-du1084lyn</a></p>
  <p id="rTAt"><a href="https://www.codementor.io/@dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc" target="_blank">https://www.codementor.io/@dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc</a></p>
  <p id="kbF8"><a href="https://learn.javascript.ru/object-methods" target="_blank">https://learn.javascript.ru/object-methods</a></p>
  <p id="4PtI"><a href="https://learn.javascript.ru/arrow-functions" target="_blank">https://learn.javascript.ru/arrow-functions</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ohyr.dev/js-scope-and-closures</guid><link>https://blog.ohyr.dev/js-scope-and-closures?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/js-scope-and-closures?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>Замикання і область видимості в JS</title><pubDate>Tue, 15 Dec 2020 22:17:58 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/1b/82/1b824e05-055c-4d93-85d3-c743e8e2bae5.png"></media:content><category>JS</category><description><![CDATA[<img src="https://img3.teletype.in/files/ac/cb/accbec97-a75b-466c-b108-2656ee5279c9.jpeg"></img>Перед розглядом складних замикань давайте розглянемо більш прості види областей видимості.]]></description><content:encoded><![CDATA[
  <figure id="6viv" class="m_column" data-caption-align="center">
    <img src="https://img3.teletype.in/files/ac/cb/accbec97-a75b-466c-b108-2656ee5279c9.jpeg" width="455" />
    <figcaption>Мемів про замикання також досить багато</figcaption>
  </figure>
  <p id="ceUO">Перед розглядом складних замикань давайте розглянемо більш прості види областей видимості.</p>
  <p id="rFef"></p>
  <h2 id="5om1">Область видимості</h2>
  <p id="FMh5">Тож Область видимості (<em>scope</em>) - це концепт в JS, який визначає <strong>доступність </strong>змінних і функцій в тому чи іншому місці коду. Ця концепція лежить також в основі <strong>замикань</strong>, поділяючи змінні на локальні та глобальні.</p>
  <p id="DHih">В JS існує кілька видів області видимості. При цьому варто зазначити, що вони звичайно ж можуть бути вкладеними.</p>
  <h3 id="ytV2">Блоковий скоуп</h3>
  <p id="YrZe">Блоки коду, як if, for, while, або ж штучні (<code>{ }</code>) визначають нову область видимості для змінних, оголошених з використанням <code>let</code> і <code>const</code>. <code>var</code> натомість не має блокової області видимості - змінні, оголошені з використанням <code>var</code>, доступні майже скрізь завдяки механізму <strong>hoisting</strong>&#x27;а<strong>.</strong></p>
  <h3 id="62DE">Скоуп функції</h3>
  <p id="gsYK">Функція, змісно ж, також має власну область видимості. Що правда, вона також працює і для <code>var&#x27;ів</code> навідміну від блокового скоупу. При цьому не важливо, функція оголошена як <code>function declaration</code> чи <code>function expression</code>.</p>
  <h3 id="Ab4M">Скоуп модуля</h3>
  <p id="I4f3">Логічно, що і у модулів також є свій скоуп. Якщо ми імпортували модуль, але він не експортує будь-яких змінних, ми не зможемо отримати до них доступ:</p>
  <pre id="Jv6R" data-lang="javascript">// модуль circle 
const pi = 3.14; 
console.log(pi); // 3.14 
// кінець модуля circle

// інший модуль
import &#x27;./circle&#x27; 
console.log(pi) // ReferenceError
// кінець іншого модуля</pre>
  <h3 id="C0SL">Глобальний скоуп</h3>
  <p id="32ws">Це найвища область видимості, яка обмежується скриптом. Вона, до речі, створена для того, щоб ми могли мати доступ до глобальних обʼєктів типу <code>Window</code> або <code>document</code>.</p>
  <p id="8jwo">Насправді існує ще одна область видимості - лексична. Саме про це і поговоримо далі, в контексті замикань - так це простіше зрозуміти.</p>
  <p id="IiJT"></p>
  <h2 id="HatW">Замикання</h2>
  <p id="HIR8">Замикання - це функції, які <strong>посилаються на незалежні змінні</strong>. Тобто, функція, визначена у замиканні, запамʼятовує &quot;<strong>середовище</strong>&quot;, в якому була створена. Незалежні змінні тут - це ті змінні, які були <strong>використані</strong> у функції як аргументи і <strong>не були створені</strong> у ній локально.</p>
  <p id="s4pf">Отже, маємо такий приклад:</p>
  <pre id="NSQ2" data-lang="javascript">function makeWorker() {
    let name = &quot;Pete&quot;;
    
    return function() {
        console.log(name);
    };
};

let name = &quot;John&quot;;

let work = makeWorker();
work(); // &quot;Pete&quot;, &quot;John&quot; чи взагалі помилка?</pre>
  <p id="vkqu">На співбесідах достатньо сказати те, що я написав вище, а також, можливо, показати цей приклад. А його суть, а також те, як це працює, я опишу далі.</p>
  <p id="FGwA"></p>
  <h2 id="mEMw">Лексичне середовище</h2>
  <p id="46ay">Що таке <strong>змінна </strong>в JS? Непоганий початок, так? Насправді, змінних <strong>не існує</strong>. Зате існує таке поняття як <strong>лексичне середовище </strong>(<em>Lexical Environment</em>). В JS у кожної функції, виконуваного блоку коду чи скрипта є деякий <strong>обʼєкт</strong>, який має назву лексичне середовище. Цей обʼєкт складається з двох частин: </p>
  <ul id="4llV">
    <li id="NVHE"><strong>Environment Record</strong> - обʼєкт, у якому всі створені в рамках даного блоку коду змінні зберігаються у якості полів;</li>
    <li id="2w8a"><strong>Посилання </strong>на зовнішнє лексичне середовище. У глобального дорівнює <code>null</code>.</li>
  </ul>
  <p id="qL5Q">Отже, змінна - це насправді <strong>поле </strong>деякого обʼєкта, який звʼязаний з тим <strong>блоком коду</strong>, в якому ця змінна <strong>оголошена</strong>.</p>
  <figure id="jZV5" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/e8/65/e865b026-ae20-4cba-afea-f2db0b4f8be7.png" width="463" />
    <figcaption>Приклад глобального лексичного середовища</figcaption>
  </figure>
  <p id="3g8K">Це якщо казати про змінні. А як щодо функцій (<code>function declaration</code>) і змінних, оголошених з використанням <code>var</code>? Насправді, все просто: перед початком виконання коду вони <strong>вже зберігаються</strong> у глобальному лексичному середовищі, дякуючи нашому улюбленому механізму <code>hoisting&#x27;у</code>.</p>
  <figure id="Os8Z" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/bd/11/bd119752-ddbe-4736-8f69-2ca613055a9b.png" width="543" />
    <figcaption>Функції у глобальному лексичному середовищі</figcaption>
  </figure>
  <p id="uNjy">Тепер поговоримо про <strong>вкладені </strong>лексичні середовища.</p>
  <figure id="XoxL" class="m_original" data-caption-align="center">
    <img src="https://teletype.in/files/f2/81/f2810dc5-5e4a-4e2d-bc52-28f3e11876c0.png" width="695" />
    <figcaption>Приклад вкладеного лексичного середовища</figcaption>
  </figure>
  <p id="0VTM">Під час виклику функції у нас, крім глобального лексичного середовища, зʼявляється ще одне - те, яке створюється для функції, що ми її викликаємо. Звідси можемо зробити висновок: коли код хоче отримати доступ до якої-небудь змінної, спочатку відбувається її <strong>пошук </strong>у внутрішньому лексичному середовищі функції, потім у зовнішньому, і так далі аж до глобального, поки змінна не буде знайдена. Саме тому якщо функція хоче отримати доступ до значення деякої змінної, вона завжди спершу шукає його у своєму власному лексичному середовищі. Тобто відповідь до першого фрагменту коду буде така:</p>
  <pre id="3qBm" data-lang="javascript">function makeWorker() {
    let name = &quot;Pete&quot;;
    
    return function() {
        console.log(name);
    };
};

let name = &quot;John&quot;;

let work = makeWorker();
work(); // &quot;Pete&quot;</pre>
  <blockquote id="XRI2">Варто зазначити, що лексичне середовище створюється <strong>кожен раз</strong> перед<strong> викликом</strong> функції, тобто новий виклик = нове лексичне середовище.</blockquote>
  <p id="STtk"></p>
  <h2 id="17fD">Вкладені функції</h2>
  <p id="QQMS">Тепер ближче до теми замикань. Іноді ми створюємо функцію, яка повертає нову функцію. Та функція, що всередині, називається <strong>вкладеною</strong>:</p>
  <pre id="l9P2" data-lang="javascript">function makeCounter() { 
    let count = 0;
    
    return function() { 
        return count++; // є доступ до зовнішньої змінної count 
    }; 
} 

let counter = makeCounter(); 

alert(counter()); // 0 
alert(counter()); // 1 
alert(counter()); // 2</pre>
  <p id="TBV8">Як це працює? При виконанні функції <code>counter</code> починається <strong>пошук </strong>змінної <code>count</code>, яка буде знайдена у батьківському лексичному середовищі. Тож є два питання:</p>
  <ul id="IR7L">
    <li id="YHDd">Чому значення змінної <code>count</code> <strong>зберігається</strong>? Ти ж казав, що кожного разу при виклику функції створюється нове лексичне середовище...</li>
    <li id="QfCY">Чи буде ця змінна <strong>спільною</strong>, якщо ми створимо <strong>два</strong> таких лічильники:</li>
  </ul>
  <pre id="vQfQ" data-lang="javascript">    let counter1 = makeCounter(); 
    let counter2 = makeCounter(); 
    
    alert(counter1()); // 0 
    alert(counter1() ; // 1 
    alert(counter2()); // 1 чи 0?</pre>
  <p id="4fcT">Отже, по-перше, всі функції при &quot;народженні&quot; отримують <strong>приховане поле </strong>(функції ж насправді - об&#x27;єкти) під іменем <code>[[Environment]]</code>, яке містить посилання на лексичне середовище місця, де функція була створена. Таким чином, <strong>функції</strong> <strong>знають, де вони були створені</strong>. Саме тому, коли ми визначаємо функцію <code>makeCounter</code>, у її поле <code>[[Environment]]</code> записується посилання на глобальне лексичне середовище. Далі ми викликаємо цю функцію і записуємо результат її виконання у змінну <code>counter</code>, яка вже, до речі, є у глобальному середовищі, але ще не задана (містить <code>undefined</code>).</p>
  <figure id="jmst" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/5a/75/5a75bf63-6752-4f73-aec4-0efeb5181504.png" width="723" />
    <figcaption>counter вже є у глобальному лексичному середовищі</figcaption>
  </figure>
  <p id="xnZp">При виклику <code>makeCounter</code> створюється її лексичне середовище, в якому є поле <code>count</code>. Далі ми створюємо анонімну функцію, яка одразу ж отримує посилання на батьківське лексичне середовище, тобто лексичне середовище <code>makeCounter</code>, і повертаємо її. </p>
  <figure id="s3rk" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/25/f8/25f8e614-8893-41e3-bf8e-194be1d79367.png" width="793" />
    <figcaption>Анонімна функція отримує посилання на лексичне середовище makeCounter зі змінною сount</figcaption>
  </figure>
  <p id="twTa">Отже, ми записали вкладену функцію у змінну <code>counter</code> і тепер хочемо її викликати. Оскільки у цій функції записане посилання на лексичне середовище <code>makeCounter</code>, вона звертається при всіх своїх викликах до однієї і тої ж змінної <code>counter</code>. Тобто, так, при виклику counter кожен раз <strong>саме для неї</strong> створюється <strong>нове</strong> лексичне середовище, але <strong>батьківське</strong> натомість залишається <strong>тим самим</strong>. Це відповідь на <strong>перше питання</strong>.</p>
  <figure id="l1Ot" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/47/95/4795237c-a974-4ba3-a1ec-57f43f112ac1.png" width="768" />
    <figcaption>Функція в counter має посилання на батьківське лексичне середовище</figcaption>
  </figure>
  <p id="2Vhz">І <strong>друге питання</strong>: чи буде ця змінна <strong>спільною</strong> для <strong>різних лічильників</strong>. Відповідь - <strong>ні</strong>, тому що як раз тут при кожному виклику <code>makeCounter</code> створюється нове лексичне середовище, в якому зберігається початкове значення змінної <code>counter</code>, унікальної для кожного виклику.</p>
  <p id="5sQq">Ось тепер маємо відповідь на найперше питання - що ж таке <strong>замикання</strong>. Замикання - це функція, яка <strong>запамʼятовує </strong>свої зовнішні змінні і може отримати до них доступ. І найголовніше - в JS <strong>усі функції</strong> за замовчуванням є замиканнями. А ось лексичне середовище, по суті - це реалізація механізму області видимості (<em>Scope</em>).</p>
  <p id="vYAQ"></p>
  <h2 id="kUch">Блоки коду</h2>
  <p id="L6Kb">Попередні приклади зосереджувались на <strong>функціях</strong>, але лексичне середовище існує для <strong>будь-якого</strong> <strong>блоку коду</strong> (інакше як би все працювало без змінних :О).</p>
  <p id="zIbD">Лексичне середовище створюється при виконанні блоку коду і містить локальні змінні цього блоку. Наприклад:</p>
  <figure id="qyUk" class="m_column" data-caption-align="center">
    <img src="https://teletype.in/files/bc/0b/bc0bf94d-9fdf-4005-8946-52f30503188d.png" width="716" />
    <figcaption>Лексичне середовище для блоку if</figcaption>
  </figure>
  <p id="Ts5w">А ось приклад для циклів:</p>
  <pre id="D9Pp" data-lang="javascript">for (let i = 0; i &lt; 10; i++) {
    let x = i + 1;
} 
alert(x); // Помилка, немає такої змінної</pre>
  <p id="Xsrk">Тут особливість в тім, що, <strong>по-перше</strong>, на кожній ітерації циклу створюється <strong>нове лексичне середовище</strong>, а <strong>по-друге</strong>, хоч і здається, що змінна <code>i</code> - лічільник циклу, візуально знаходиться не в його тілі, насправді це <strong>один і той же</strong> блок коду.</p>
  <p id="rOw3"></p>
  <h2 id="SJcj">IIFE</h2>
  <p id="axoE">В минулому в JS <strong>не було</strong> лексичного середовища на рівні блоків коду, тому вигадали &quot;костиль&quot;. Оскільки лексичне середовище працювало лише для <strong>функцій</strong>, їх почали використовувати, щоб створити ізольований блок коду:</p>
  <pre id="TsNp" data-lang="javascript">(function() { 
    let message = &quot;Hello&quot;; 
    alert(message); // Hello 
})();</pre>
  <p id="Lb9F">Так, тут ми просто створюємо анонімну функцію, яку одразу ж виконуємо, все просто. Це має назву <em>immediately invoked function expression </em>- <strong>IIFE</strong>.</p>
  <p id="YGi4">Сейчас так делать не нужно, лексическое окружение создается для любого блока кода.</p>
  <p id="JwmF"></p>
  <h2 id="qqIZ">Збірка сміття</h2>
  <p id="LwK1">Зазвичай лексичне середовище <strong>видаляется одразу ж</strong> після того, як функція чи блок коду виконались:</p>
  <pre id="heiu" data-lang="javascript">function f() { 
    let value1 = 123; 
    let value2 = 456; 
} 

f();</pre>
  <p id="OfFs">Але якщо провернути трюк зі <strong>вкладеною функцією</strong> - у неї залишиться посилання на лексичне середовище батьківської функції, тому воно просто не зможе видалитись, поки не видалиться вкладена функція. У наступному прикладі зберігаються одразу ж т<strong>ри лексичні середовища</strong>:</p>
  <pre id="dQTS" data-lang="javascript">function f() { 
    let value = Math.random(); 
    return function() { 
        alert(value); 
    }; 
} 

// Три функції в масиві, кожна з яких посилається на 
// лексичне середовище з відповідного виклику f() 
let arr = [f(), f(), f()];</pre>
  <p id="hq9U"></p>
  <h2 id="qSVH">Сюрпризи в дебагері</h2>
  <p id="kvnc">Єдине, про що варто також зазначити - приколи дебагера. Якщо ви якось захочете скористатись <strong>дебагером </strong>у браузері з двигуном <code>V8</code>, знайте, що він любить все оптимізувати, тому при відладці можуть бути <strong>сюрпризи</strong>:</p>
  <pre id="OmRl" data-lang="javascript">let value = &quot;Сюрприз!&quot;; 

function f() { 
    let value = &quot;найближче значення&quot;; 
    
    function g() { 
        debugger; // в консолі напишіть alert(value): отримаєте &quot;Сюрприз!&quot; 
    }
     
    return g; 
} 

let g = f(); 
g();</pre>
  <p id="HC6O">або ж:</p>
  <pre id="hbJ7" data-lang="javascript">function f() { 
    let value = Math.random(); 
    
    function g() { 
        debugger; // в консолі напишіть alert(value): такої змінної немає! 
    }
     
    return g; 
} 

let g = f(); 
g();</pre>
  <p id="9PPJ">Ще раз: такі приколи зʼявляються <strong>лише у відладці</strong>, при звичайній роботі все буде ок.</p>
  <p id="GV7t"></p>
  <h2 id="FgsC">Висновки</h2>
  <blockquote id="1RGi">В JS існує <strong>5 видів області видимості</strong>: блокова, функція, модуль, глобальна, а також лексична. До блокових відносяться всі конструкції циклів, умовні конструкції, а також штучні блоки коду.</blockquote>
  <blockquote id="FHxV">Глобальна область видимості створена для роботи з <strong>полями глобального обʼєкту</strong>: <code>Window</code>, <code>document</code>, <code>Math</code>...</blockquote>
  <blockquote id="V1NL">Просте визначення замикання: замикання - це функції, що посилаються на <strong>незалежні змінні</strong>. Таким чином, функція, що визначена у <strong>іншій функції</strong> (замиканні) запамʼятовує <strong>середовище</strong>, в якому була створена.</blockquote>
  <blockquote id="LCIJ">Під капотом замикання засновані на механізмі <strong>лексичного середовища</strong> (<em>Lexical Environment</em>).</blockquote>
  <blockquote id="fI4w">В JS <strong>всі функції</strong> є замиканнями (і <strong>стрілкові</strong> теж), а лексичне середовище по суті - це реалізація <strong>механізму області видимості</strong> (<em>Scope</em>).</blockquote>
  <blockquote id="ZfB7">У блоків коду в JS, таких як цикли, умовні конструкції, а також у штучних блоків коду (<code>{ }</code>) <strong>також</strong> є лексичне середовище.</blockquote>
  <blockquote id="mwtw">Раніше у блоків коду <strong>не було</strong> власного лексичного середовища, тому використовували лайфхак: створювали <strong>анонімні функції</strong> і одразу ж <strong>викликали</strong> їх. Це зветься <em>immediately invoked function expression </em>(<strong>IIFE</strong>).</blockquote>
  <blockquote id="L6LB">Лексичне середовище <strong>видаляється</strong> одразу ж після закінчення виконання функції чи блоку. Але якщо у скоупі відповідної функції є <strong>вкладена</strong> <strong>функція</strong> - <strong>батьківське</strong> лексичне середовище <strong>буде жити</strong> допоки на нього існують <strong>посилання</strong>.</blockquote>
  <p id="6uam"></p>
  <h2 id="MwNR">Джерела</h2>
  <p id="zk9z"><a href="https://learn.javascript.ru/closure" target="_blank">https://learn.javascript.ru/closure</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.ohyr.dev/js-data-types</guid><link>https://blog.ohyr.dev/js-data-types?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity</link><comments>https://blog.ohyr.dev/js-data-types?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=dusty3ntity#comments</comments><dc:creator>dusty3ntity</dc:creator><title>Типи даних в JS</title><pubDate>Tue, 15 Dec 2020 13:17:19 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/e2/5b/e25b297f-031f-4e5a-90d5-67fe522e550b.png"></media:content><category>JS</category><description><![CDATA[<img src="https://i.stack.imgur.com/T9M2J.png"></img>JS - динамічно-типізована мова програмування. Це означає, що типи у ній є, але змінні можуть містити в собі будь-які дані незалежно від типу.]]></description><content:encoded><![CDATA[
  <p id="srXi">JS - <strong>динамічно-типізована</strong> мова програмування. Це означає, що типи у ній є, але змінні можуть містити в собі будь-які дані <strong>незалежно</strong> від типу.</p>
  <p id="mb6q">Всього у JS <strong>вісім</strong> типів даних:</p>
  <ul id="oShZ">
    <li id="tVpE">number</li>
    <li id="iRvf">BigInt</li>
    <li id="avbH">string</li>
    <li id="4Ne5">boolean</li>
    <li id="8RtV">null</li>
    <li id="xIsr">undefined</li>
    <li id="xAoW">object</li>
    <li id="Xf3S">Symbol</li>
  </ul>
  <p id="wqcp">Незважаючи на те, що <strong>null </strong>і <strong>undefined </strong>залогікою повинні мати тип <strong>object</strong>, вони є окремими типами. Так склалось. </p>
  <p id="e4ZJ"><strong>null</strong> - це особливе значення, яке буквально означає &quot;у змінну записане <strong>нічого&quot;</strong> - тобто значення невідоме. Важливо, что це <strong>не є посиланням</strong> на &quot;нічого&quot;, як у інших мовах програмування.</p>
  <p id="c3mm"><strong>undefined</strong> - це також особливе значення, яке вказує на те, що значення змінної ще не було задане. За замовчуванням лежить у всіх &quot;незаданих&quot; змінних, якщо:</p>
  <ul id="PCWc">
    <li id="8bnV">У змінну не записане значення при створенні;</li>
    <li id="FCsK">Функція нічого не повертає, але ми хочемо отримати щось від неї (саме тому ми бачимо <code>undefined</code> після створення функції у консолі браузера).</li>
  </ul>
  <p id="PhSd">Зазвичай для маніпуляції з &quot;порожніми&quot; зміниими використовують саме <strong>null</strong>, а ось <strong>undefined </strong>використовується тільки для того, щоб перевірити, чи було взагалі значення змінній задане.</p>
  <figure id="EtDr" class="m_column" data-caption-align="center">
    <img src="https://i.stack.imgur.com/T9M2J.png" width="1024" />
    <figcaption>Порівняння null і undefined на прикладі</figcaption>
  </figure>
  <p id="qXNb"></p>
  <h2 id="RjAl">Примітиви і object</h2>
  <p id="cS2h">Всі типи в JS, <strong>крім object</strong>, є примітивами. Це означає, що обʼєкти зберігають в собі &quot;набір даних&quot;, а примітивні типи, як-от рядки - представляють самі себе. Ба більше, при своренні обʼєкту ми отримуємо посилання на нього, а при створенні примітиву - саме значення. Тобто такий фокус не пройде:</p>
  <pre id="9nz1" data-lang="javascript">let s = &quot;hello&quot;;
let c = s;
c += &quot; world&quot;;
console.log(s) // все ще &quot;hello&quot;</pre>
  <p id="suTw">Звичайно ви можете (і маєте) заперечити: <em>&quot;Але ж у рядків є методи і навіть поля, як-от length! Чому ж тоді це примітив???&quot;. </em>І цьому є появнення<em>.</em></p>
  <p id="Ryh3">Примітиви створені для <strong>оптимізації</strong>. Але для зручності в JS також є і <strong>класи</strong>, які представляють &quot;обʼєктні&quot; аналоги цих примітивів. Тобто в JS є такі класи як <code>Number</code>, <code>String</code>, <code>Boolean</code>, і вони також мають конструктори, як і в інших мовах.</p>
  <p id="kBKi">Таким чином, коли ми хочемо, наприклад, отримати значення поля <code>length</code> або викликати метод <code>toUpperCase</code> у рядка, під капотом створюється <strong>обʼєкт-обгортка</strong> для рядка, викликається метод, повертається значення і обʼєкт <strong>видаляється</strong>. </p>
  <p id="5Bh3">Я сказав, що є класи <code>Number</code>, <code>String</code>... Так ось, екземпляри цих класів можна без проблем створити з використанням конструкторів:</p>
  <pre id="1mdA" data-lang="javascript">const s = new String(&quot;hello&quot;);
console.log(typeof s); // object</pre>
  <p id="p808">Це інколи буває корисно, але взагалі-то не рекомендується так робити, і причину цього я вказав на другому рядку фрагменту коду вище - стрічка більше не стрічка, а <code>object</code>. Замість цього часто роблять так:</p>
  <pre id="AffO" data-lang="javascript">const numString = &quot;123&quot;;
const num = Number(numString); // 123</pre>
  <p id="MaFO">Як бачимо, це швидкий варіант парсингу.</p>
  <p id="iErV"></p>
  <h2 id="76e7">Помилки typeof</h2>
  <p id="Ypvf">І наостанок кілька неточностей в JS:</p>
  <ul id="I3db">
    <li id="XBVf"><code>typeof(null); // &quot;object&quot;</code> - <code>null</code> - це окремий тип;</li>
    <li id="SId0"><code>typeof(alert); // &quot;function&quot;</code> - типу <code>function</code> не існує, це зроблено для зручності розробки.</li>
  </ul>
  <p id="ek8r"></p>
  <h2 id="ahoq">Висновки</h2>
  <blockquote id="tlKK">JS - динамічно-типізована мова програмування - змінні можуть містити в собі дані будь-якого типу.</blockquote>
  <blockquote id="Noev">В JS нараховується <strong>вісім</strong> типів даних: <code>number</code>, <code>BigInt</code>, <code>string</code>, <code>boolean</code>, <code>null</code>, <code>undefined</code>, <code>object</code>, <code>Symbol</code>.</blockquote>
  <blockquote id="jwww"><code>null</code> і <code>undefined</code> - це окремі типи, незалежні від <code>object</code>. <code>null</code> зазвичай вказує на те, що змінні присвоєне &quot;<strong>нічого</strong>&quot;, а <code>undefined</code> - що змінній взагалі нічого не було присвоєно. Також <code>undefined</code> <strong>за замовчуванням</strong> повертається з функцій, які нічого не повертають.</blockquote>
  <blockquote id="a9QB">Всі типи, окрім <code>object</code>, є <strong>примітивами</strong>. Те, що, до прикладу, у рядків, є методи і поля, пояснюється тим, що при зверненні до них, рядки під капотом <strong>конвертують</strong> у їх обʼєктні репрезентації.</blockquote>
  <blockquote id="HLyC">Використовувати обʼєктні репрезентації замість примітивних аналогів вкрай <strong>не рекомендується</strong>, бо тип у них буде <code>object</code>. Замість цього, до прикладу, для парсингу, краще використовувати відповідні функції, як-от <code>Number()</code>.</blockquote>
  <blockquote id="glAr">У оператора typeof є кілька неточностей:<br />- <code>typeof(null); // &quot;object&quot;</code> - <code>null</code> - це окремий тип;<br />- <code>typeof(alert); // &quot;function&quot;</code> - типу <code>function</code> не існує, це зроблено для зручності розробки.</blockquote>
  <p id="mvvU"></p>
  <h2 id="jKu7">Джерела</h2>
  <p id="bReo"><a href="https://learn.javascript.ru/types" target="_blank">https://learn.javascript.ru/types</a></p>

]]></content:encoded></item></channel></rss>