Інтернет Windows Android

Нескінченний цикл JavaScript. Javascript цикли while, do-while та for

Цикл - це інструкція, що управляє, що дозволяє повторювати виконання програмного коду певну кількість разів. Кожне окреме виконання інструкцій у тілі циклу називається ітерацією.

Цикл while

Синтаксис циклу while:

Вираз у круглих дужках називається умовою виконання циклу або коротко умовою. Спочатку обчислюється значення виразу. Отримане значення, якщо необхідно, неявно перетворюється на булеву типу. Якщо результатом обчислення виразу є значення true , то інструкція, розташована в тілі циклу, виконується, потім керування передається на початок циклу і обчислюється умова знову. Якщо результатом обчислення виразу є false , інтерпретатор завершує роботу циклу і переходить до виконання інструкції наступної за циклом. Таким чином, інтерпретатор знову і знову виконує код розташований у тілі циклу, доки умова залишається істинною:

Var i = 0; while (i< 3) { // Выполнять код, пока значение переменной i меньше 3 alert("i: " + i); i++; // Увеличиваем значение переменной i }

Цикл do-while

Синтаксис циклу do-while:

Цикл do-while схожий на цикл while , крім того, що перевірка умови виконання циклу проводиться після першої ітерації, а не перед нею, і завершується цикл крапкою з комою. Оскільки умова перевіряється після ітерації, код у тілі циклу do-while завжди виконується щонайменше один раз:

Var count = 0; do ( document.write(count + " "); count++; ) while(count< 5); Попробовать »

Цей цикл може бути корисним, коли код у тілі циклу повинен бути виконаний хоча б один раз, незалежно від умови виконання.

Цикл for

Синтаксис циклу for:

У циклі for розташовуються три вирази, що розділяються крапкою з комою. Ці три вирази мають такий порядок виконання:

  • Перший вираз завжди обчислюється лише один раз – перед першою ітерацією. Тому зазвичай як перший вираз виступає визначення змінної, яка використовується в умові виконання циклу як лічильник.
  • Другий вираз визначає умову виконання циклу. Воно обчислюється перед кожною ітерацією та визначає, чи виконуватиметься тіло циклу. Якщо результатом обчислення виразу є справжнє значення, програмний код у тілі циклу виконується. Якщо повертається помилкове значення, виконання циклу завершується і керування переходить до наступної після циклу інструкції. Якщо при першій перевірці умови воно виявляється помилковим, код у тілі циклу не виконається жодного разу.
  • Після кожної ітерації обчислюється третій вираз. Зазвичай його використовують для зміни значення змінної, яка використовується для перевірки умови виконання циклу.
  • Приклад циклу for:

    For (var count = 0; count< 5; count++) document.write(count + " "); Попробовать »

    Як видно з прикладу, цикл for на відміну від інших циклів дозволяє згрупувати пов'язаний із циклом код в одному місці.

    Будь-який з виразів у циклі for може бути відсутнім, однак самі крапки з комою обов'язково повинні бути присутніми, інакше буде синтаксична помилка. За відсутності другого виразу цикл виконуватиметься нескінченно.

    Var i = 0; for (; i< 4; i++) ... var i = 0; for (; i < 4;) ... for (var i = 1; /* нет условия */ ; i++) ... // Это эквивалентно следующему коду for (var i = 1; true; i++) ...

    Замість одного виразу можна вказати кілька виразів, розділяючи їх оператором кома.

    // не виконається, оскільки у перевірці умови останній вираз false for (i = 1; i< 4, false; i++) ... for (var i = 1, j = 5; i , що дозволяє вибрати кілька елементів). Цикл for оголошує змінну i і задає їй значення 0. Також він перевіряє, що i менше кількості елементів в елементі виконує оператор if і збільшує i на один після кожного проходу циклу.

    Виберіть деякі жанри музики, а потім натисніть кнопку нижче: R&B Jazz Blues New Age Classical Opera

    function howMany(selectObject) ( var numberSelected = 0; for (var i = 0; i< selectObject.options.length; i++) { if (selectObject.options[i].selected) { numberSelected++; } } return numberSelected; } var btn = document.getElementById("btn"); btn.addEventListener("click", function(){ alert("Выбрано элементов: " + howMany(document.selectForm.musicTypes)) });

    Цикл do...while

    Цикл do...while повторюється поки що задана умова істинна. Оператор do...while має вигляд:

    Do виразу while (умова);

    вирази виконуються поки що умова істинна. Щоб використати кілька виразів, використовуйте блок-вираз ( ... ), щоб згрупувати їх. Якщо умова істинна, вирази виконаються знову. Наприкінці кожного проходу умова перевіряється. Якщо умова хибна, виконання зупиняється і керування передається виразу після do...while .

    приклад

    У наступному прикладі цикл do виконається мінімум 1 раз і запускається знову, поки i менше 5.

    Do ( i += 1; console.log(i); ) while (i< 5);

    Цикл while

    Цикл while виконує вирази поки умова істинна. Виглядає він так:

    While (умова) виразу

    Якщо умова стає хибною, вирази в циклі перестають виконуватися і управління переходить до виразу після циклу.

    Умова перевіряється на істинність доти, як виконуються висловлювання у циклі. Якщо умова є істинною, виконуються вирази, а потім умова перевіряється знову. Якщо умова помилкова, виконання зупиняється і управління переходить до виразу після while .

    Щоб використати кілька виразів, використовуйте блок вираз ( ... ), щоб згрупувати їх.

    Приклад 1

    Наступний цикл while працює, поки n менше трьох:

    Var n = 0; var x = 0; while (n< 3) { n++; x += n; }

    З кожною ітерацією цикл збільшує n і додає це значення до x . Тому x і n отримують наступні значення:

    • Після першого проходу: n = 1 та x = 1
    • Після другого: n = 2 та x = 3
    • Після третього проходу: n = 3 та x = 6

    Після третього проходу умова n< 3 становится ложным, поэтому цикл прерывается.

    Приклад 2

    Уникайте нескінченних циклів. Переконайтеся, що умова циклу врешті-решт стане хибним; інакше цикл ніколи не перерветься. Висловлювання у наступному циклі while виконуватимуться вічно, т.к. умова ніколи не стане хибною:

    While (true) ( ​​console.log("Hello, world"); )

    Мітка (label)

    Мітка є оператором з ідентифікатором, який дозволяє вам посилатися на якесь місце у вашій програмі. Наприклад, ви можете використовувати мітку, щоб позначити цикл, а потім використовувати оператори break або continue, щоб вказати, чи повинна програма переривати цикл або продовжувати його виконання.

    Синтаксис мітки наступний:

    Мітка: оператор

    Значення міткиможе бути будь-яким коректним JavaScript індентифікатором, який не є зарезервованим словом. Оператор , вказаний вами після мітки може бути будь-яким виразом.

    приклад

    У цьому прикладі мітка markLoop позначає цикл while .

    MarkLoop: while (theMark == true) ( ​​doSomething(); )

    break

    Використовуйте оператор break , щоб переривати цикл, перемикати управління або у поєднанні з оператором мітка.

    • Коли ви використовуєте break без мітки, він перериває цикли while , do-while і for або відразу перемикає керування до наступного виразу.
    • Коли ви використовуєте break з міткою, він перериває спеціально зазначений вираз.

    Синтаксис оператора може бути таким:

  • break;
  • break Мітка;
  • Перша форма синтаксису перериває цикл зовсім або перемикає керування; друга перериває спеціально позначений вираз.

    Приклад 1

    Наступний приклад проходить по елементах у масиві, доки не знайде елемент, чиє значення - theValue:

    For (i = 0; i< a.length; i++) { if (a[i] == theValue) { break; } }

    Приклад 2: Переривання мітки var x = 0; var z = 0 labelCancelLoops: while (true) ( ​​console.log("Зовнішній цикл: " + x); x += 1; z = 1; while (true) ( ​​console.log("Внутрішній цикл: " + z) ; z += 1; if (z === 10 && x === 10)

    Оператор continue використовується, щоб зробити крок вперед в циклах while , do-while , for або перейти до мітки.

    • Коли ви використовуєте continue без мітки, він перериває поточну ітерацію циклів while , do-while та for і продовжує виконання циклу з наступної ітерації. На відміну від break, continue не перериває виконання циклу повністю. У циклі, коли він стрибає до умови. А в for збільшує крок.
    • Коли ви використовуєте continue з міткою, він застосовується до циклу з цією міткою.

    Синтаксис continue може виглядати так:

  • continue;
  • continue Мітка ;
  • Приклад 1

    Наступний приклад показує цикл while з оператором continue , який спрацьовує, коли значення i дорівнює 3. Таким чином, n отримує значення 1, 3, 7 та 12.

    Var i = 0; var n = 0; while (i< 5) { i++; if (i == 3) { continue; } n += i; }

    Приклад 2

    Вираз, зазначений checkiandjмістить вираз відзначений checkj. При зустрічі з continue програма перериває поточну ітерацію checkjі розпочинає наступну ітерацію. Щоразу при зустрічі з continue , checkjпереходить на наступну ітерацію, доки умова повертає false . Коли повертається false, після обчислення залишку від поділу checkiandj, checkiandjпереходить на наступну ітерацію, доки його умова повертає false . Коли повертається false, програма продовжує виконання з виразу після checkiandj.

    Якщо continue проставлена ​​мітка checkiandj, програма може продовжитися з початку мітки checkiandj.

    Checkiandj: while (i< 4) { console.log(i); i += 1; checkj: while (j >4) ( console.log(j); j -= 1; if ((j % 2) != 0) ( continue checkj; ) console.log(j + "парне."); ) console.log("i = "+ i);

    console.log("j="+j);

    )

    for...in

    Оператор for...in проходить за всіма властивостями об'єкта. JavaScript виконає вказані вирази для кожної окремої властивості. Цикл for...in виглядає так:

    For (variable in object) (вирази)

    приклад
    Наступна функція бере своїм аргументом об'єкт та його ім'я. Потім проходить по всіх властивостях об'єкта і повертає рядок, який містить імена властивостей та їх значення.

    Function dump_props(obj, obj_name) ( var result = ""; for (var i in obj) ( result += obj_name + "." + i + " = " + obj[i] + " "; ) result += ""; return result; )Для об'єкта car з властивостями make і model

    результатом

    буде :

    Car.make = Ford car.model = Mustang

    Let obj = (модель: "AUDI A8", рік: "2019", колір: "білий") for (key in obj) ( console.log(`$(key) = $(obj)`); ) // model = AUDI A8 // year = 2019 // color = brown

    Масиви

    Хоча, дуже привабливо використовувати for...in як спосіб пройтись по всіх елементах Array, цей оператор повертає ім'я властивостей, визначених користувачем, крім числових індексів. Таким чином, краще використовувати стандартний for для числових індексів при взаємодії з масивами, оскільки оператор for...in проходить за певними користувачами властивостями на додаток до елементів масиву, якщо ви змінюєте масив, наприклад, додаєте властивості та методи.

    For ( variable of object) { вирази}

    Наступний приклад показує різницю між циклами for...of та for...in. Тоді як for...in проходить за іменами властивостей, for...of проходить за значеннями властивостей:

    Let arr =; arr.foo = "hello"; for (let i in arr) (console.log(i); // виводить "0", "1", "2", "foo") for (let i of arr) (console.log(i); / / виводить "3", "5", "7")

    Що відступи вважатимуться показником складності коду (хоч і досить грубим). Самі собою відступи нейтральні, оскільки вони – лише засіб форматування тексту, але річ у тому, що вони застосовуються виділення спеціальних блоків програм, наприклад – управляючих конструкцій. Читаючи код і натикаючись на відступ, програміст змушений брати до уваги те, на що вказує відступ, пам'ятати контекст, в якому існує виділений блок. Це, природно, повторюється, якщо виділеному відступами ділянці коду з'являється ще один особливий фрагмент.

    Якщо не звертати увагу на зміст текстів, то ось як зазвичай виглядає складний код, ділянки якого схожі на літери «V», що лежать на боці, і простий код, блок якого, якщо не враховувати різну довжину рядків, схожий на прямокутник.


    Чим більше відступів – тим складніше зазвичай і код

    Конструкції, які треба виділяти відступами, будуть у коді завжди, про те, щоб зовсім позбутися їх, не йде. Однак, у наших силах зменшити складність програм, які ми пишемо, за рахунок раціонального вибору абстракцій для вирішення завдань, що постають перед нами.

    Візьмемо, наприклад, масиви. Традиційно їх обробки використовують різні види циклів. Поняття «масив» та «цикл» нерозривно пов'язані у свідомості багатьох програмістів. Однак цикл – конструкція дуже неоднозначна. Ось що пише про цикли Луїс Атенціо в книзі «Функціональне програмування в JavaScript»: «Цикл – це жорстка конструкція, що керує, яку нелегко використовувати повторно і складно зтикати з іншими операціями. Крім того, використання циклів означає появу коду, який змінюється з кожною ітерацією.


    Чи можна позбутися циклів?

    Цикл - одна з основних структурних керуючих конструкцій, і, власне, ми не збираємося говорити про те, що цикли - це зло, якого треба позбавлятися. Наша головна мета полягає у зниженні складності власного коду за рахунок мінімального використання циклів для обробки масивів. Чи це можливо? Пропонуємо з'ясувати разом.

    Ми вже говорили про те, що керуючі конструкції, на кшталт циклів, ускладнюють код. Але чому це так? Поглянемо на те, як працюють цикли JavaScript.

    У JS існує кілька способів організації циклів. Зокрема, один із базових видів циклів – це while . Перш ніж вникати у подробиці, трохи підготуємось. А саме – створимо функцію та масив, з яким працюватимемо.

    // oodlify:: String -> String function oodlify(s) ( return s.replace(//g, "oodle"); ) const input = [ "John", "Paul", "George", "Ringo", ];
    Отже, є масив, кожен елемент якого ми збираємося обробити за допомогою функції oodlify. Якщо використовувати для вирішення цього завдання цикл while , вийде таке:

    Let i = 0; const len ​​= input.length; let output =; while (i< len) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); i = i + 1; }
    Для того, щоб відстежувати поточний оброблюваний елемент масиву, використовуємо лічильник i . Необхідно ініціалізувати його нулем та збільшувати на одиницю у кожній ітерації циклу. Крім того, потрібно порівнювати його з довжиною масиву, з len для того, щоб знати, коли треба припинити роботу.

    Цей шаблон настільки поширений, що JavaScript має більш простий спосіб організувати подібну конструкцію – цикл for . Такий цикл дозволить вирішити те саме завдання наступним чином:

    Const len ​​= input.length; let output =; for (let i = 0; i< len; i = i + 1) { let item = input[i]; let newItem = oodlify(item); output.push(newItem); }
    Цикл for – корисна конструкція, тому що завдяки їй усі стандартні допоміжні операції зі лічильником виносяться у верхню частину блоку. Використовуючи while , легко забути необхідність інкрементувати лічильник i , що призведе до запуску нескінченного циклу. Безумовно, цикл для набагато зручніший за цикл while . Але давайте пригальмуємо і подивимося на те, чого намагається досягти наш код. Ми хочемо обробити за допомогою функції oodlify() , кожен елемент масиву і помістити те, що вийшло, в новий масив. Сам по собі лічильник, який використовується для доступу до елементів масиву, нас не цікавить.

    Подібний шаблон роботи з масивами, що передбачає виконання дій з кожним елементом, дуже поширений. В результаті, в ES2015 з'явилася нова конструкція циклу, яка дозволяє забути про лічильник. Це – цикл for…of. У кожній ітерації такого циклу надається наступний елемент масиву. Виглядає так:

    Let output =; for (let item of input) (let newItem = oodlify(item); output.push(newItem); )
    Код виглядає набагато чистіше. Тут немає ні лічильника, ні операції порівняння. За такого підходу навіть не потрібно звертатися до конкретного елементу масиву за індексом. Цикл for...of бере на себе всі допоміжні операції.

    Якщо завершити цьому дослідження методів роботи з масивами і використовувати цикли for…of скрізь замість циклів for , це буде непоганим кроком вперед рахунок спрощення коду. Але... ми можемо піти далі.

    Трансформація масивів Цикл for…of виглядає набагато чистіше, ніж цикл for , але з ним у коді є чимало допоміжних елементів. Так, треба ініціалізувати масив output і в кожній ітерації циклу викликати метод push(). Код можна зробити ще компактнішим і виразнішим, але перш ніж цим зайнятися, давайте трохи розширимо демонстраційне завдання. Як бути, якщо за допомогою функції oodlify() треба обробити два масиви?

    Const fellowship = ["frodo", "sam", "gandalf", "aragorn", "boromir", "legolas", "gimli",]; const band = ["John", "Paul", "George", "Ringo",];
    Цілком очевидне рішення – використовувати два цикли та обробити масиви в них:

    Let bandoodle =; for (let item of band) ( let newItem = oodlify(item); bandoodle.push(newItem); ) let floodleship = ; for (let item of fellowship) (let newItem = oodlify(item); floodleship.push(newItem); )
    Цілком робочий варіант. А код, який працює – це набагато краще, ніж код, який не вирішує поставлене перед ним завдання. Але два дуже схожі фрагменти коду не особливо добре узгоджуються з принципом розробки DRY . Код можна піддати рефакторингу для того, щоб знизити кількість повторень.

    Дотримуючись цієї ідеї, створюємо таку функцію:

    Function oodlifyArray(input) ( let output = ; for (let item of input) ( let newItem = oodlify(item); output.push(newItem); ) return output; ) let bandoodle = oodlifyArray(band); let floodleship = oodlifyArray(fellowship);
    Виглядає це набагато краще, але що якщо є ще одна функція, за допомогою якої ми теж хочемо обробляти елементи масивів?

    Function izzlify(s) ( return s.replace(/+/g, "izzle"); )
    Тепер функція oodlifyArray() не допоможе. Однак, якщо створити ще одну подібну функцію, цього разу izzlufyArray() , ми знову повторимося. Все ж таки, створимо таку функцію і порівняємо її з oodlifyArray() :

    Function oodlifyArray(input) ( let output = ; for (let item of input) let item of input) ( let newItem = izzlify(item); output.push(newItem); ) return output;
    Ці дві функції неймовірно схожі. Можливо, узагальним шаблон, яким вони йдуть? Наша мета полягає в наступному: «Є масив і функція. Потрібно отримати новий масив, до якого будуть записані результати обробки кожного з елементів вихідного масиву за допомогою функції. Подібний спосіб обробки масивів називають "відображенням" або "трансформацією" (mapping в англомовній термінології). Функції, які виконують такі операції, зазвичай називають "map". Ось як виглядає наш варіант такої функції:

    Function map(f, a) ( let output = ; for (let item of a) ( output.push(f(item)); ) return output; )
    Хоча цикл тепер і винесений в окрему функцію, зовсім позбутися його не вдалося. Якщо піти до кінця і спробувати обійтися зовсім без циклічних конструкцій, можна написати рекурсивний варіант того самого:

    Function map(f, a) ( if (a.length === 0) ( return ; ) return .concat(map(f, a.slice(1))); )
    Рекурсивне рішення виглядає дуже елегантно. Усього пара рядків коду та мінімум відступів. Але рекурсивні реалізації алгоритмів зазвичай використовують із великою обережністю, крім того, вони відрізняються поганою продуктивністю у старих браузерах. І, насправді, нам не потрібно самим писати функцію реалізації операції відображення, якщо тільки на те немає вагомої причини. Те, що робить наша функція map – завдання настільки поширене, що JavaScript має вбудований метод map() . Якщо скористатися цим методом, код виявиться таким:

    Let bandoodle = band.map(oodlify); let floodleship = fellowship.map(oodlify); let bandizzle = band.map(izzlify); let fellowshizzle = fellowship.map(izzlify);
    Тут взагалі немає ні відступів, ні циклів. Звичайно, при обробці даних, десь у надрах JavaScript, можуть використовуватися цикли, але це вже не наша турбота. Тепер код вийшов і стислим, і виразним. Крім того, він простіший.

    Чому цей код простіше? Може здатися, що це питання дурне, але подумайте про це. Він простіше тому, що він коротший? Ні. Компактність коду – це ознака простоти. Він простіше тому, що за такого підходу ми розбили завдання на частини. А саме, є дві функції, які працюють з рядками: oodlify та izzlify. Ці функції не повинні нічого знати про масиви або цикли. Є ще одна функція - map, яка працює з масивами. При цьому їй абсолютно байдуже, якого типу дані в масиві, або те, що ми хочемо з цими даними робити. Вона просто виконує будь-яку передану їй функцію, передаючи елементи масиву. Замість того, щоб все змішувати, ми розділили обробку рядків та обробку масивів. Саме тому підсумковий код виявився простішим.

    Згортка масивів Отже, функція map дуже корисна, але вона не перекриває всі варіанти обробки масивів, в яких використовуються цикли. Вона хороша у випадках, коли на основі якогось масиву потрібно створити новий, що має ту саму довжину. Але що якщо нам потрібно, наприклад, скласти всі елементи числового масиву? Або, якщо треба знайти найкоротший рядок у списку? Іноді потрібно обробити масив і фактично сформувати на його основі одне значення.

    Розглянемо приклад. Припустимо, є список об'єктів, кожен з яких представляє супергероя:

    Const heroes = [ (name: "Hulk", strength: 90000), (name: "Spider-Man", strength: 25000), (name: "Hawk Eye", strength: 136), (name: "Thor", strength: 100000), (name: "Black Widow", strength: 136), (name: "Vision", strength: 5000), (name: "Scarlet Witch", strength: 60), (name: "Mystique", strength: 120), (name: "Namora", strength: 75000),];
    Треба знайти найсильнішого героя. Для того щоб це зробити, можна скористатися циклом for…of:

    Let strongest = (strength: 0); for (let hero of heroes) ( if (hero.strength > strongest.strength) ( strongest = hero; ) )
    Враховуючи всі обставини, цей код не такий уже й поганий. Ми обходимо в циклі масив, зберігаючи об'єкт найсильнішого з переглянутих героїв у змінній strongest. Щоб ясніше побачити шаблон роботи з масивом, уявімо, що ще треба з'ясувати загальну силу всіх героїв.

    Let combinedStrength = 0; for (let hero of heroes) ( combinedStrength += hero.strength; )
    У кожному з цих прикладів є робоча змінна, яку ініціалізують перед запуском циклу. Потім, у кожній ітерації, обробляють один елемент масиву та оновлюють змінну. Для того, щоб виділити схему роботи ще краще, винесемо операції, що виконуються всередині циклів, функції, і перейменуємо змінні для того, щоб підкреслити схожість вироблених дій.

    Function greaterStrength(champion, contender) ( return (contender.strength > champion.strength) ? contender: champion; ) function addStrength(tally, hero) ( return tally + hero.strength; ) const initialStrongest = (streng let working = initialStrongest; for (hero of heroes) (working = greaterStrength(working, hero); ) const strongest = working; const initialCombinedStrength = 0; working = initialCombinedStrength; for (hero of heroes) ( working = addStrength(working, hero); ) const combinedStrength = working;
    Якщо все переписано так, як показано вище, два цикли виявляються дуже схожими. Єдине, що їх відрізняє - це функції, що викликаються в них, і початкові значення змінних. В обох циклах масив згортається до значення. В англомовній термінології така операція називається "reducing". Тому створимо функцію reduce, що реалізує виявлений шаблон.

    Function reduce(f, initialVal, a) ( let working = initialVal; for (let item of a) ( working = f(working, item); ) return working;
    Слід зазначити, що, як і у випадку з шаблоном функції map, шаблон функції reduce поширений так широко, що JavaScript надає його як вбудований метод масивів. Тому свій метод, якщо цього немає особливої ​​причини, писати не потрібно. З використанням стандартного методу код виглядатиме так:

    Const strongestHero = heroes.reduce(greaterStrength, (strength: 0)); const combinedStrength = heroes.reduce(addStrength, 0);
    Якщо придивитися до підсумкового результату, можна виявити, що код, що вийшов, не набагато коротший від того, що був раніше, економія зовсім невелика. Якби ми використовували функцію reduce, написану самостійно, то в цілому код вийшов би більше. Однак наша мета полягає не в тому, щоб писати короткий код, а в тому, щоб зменшувати його складність. Отже, чи ми зменшили складність програми? Можу стверджувати, що зменшили. Ми відокремили код циклів від коду, який обробляє елементи масиву. В результаті окремі ділянки програми стали більш незалежними. Код вийшов простіше.

    На перший погляд, функція reduce може здатися досить примітивною. Більшість прикладів використання цієї функції демонструються прості речі, на кшталт складання всіх елементів числових масивів. Однак, ніде не сказано, що значення, яке повертає reduce, має бути примітивним типом. Це може бути об'єкт чи навіть інший масив. Коли я вперше це зрозумів, це мене вразило. Можна, наприклад, написати реалізацію операції відображення або фільтрації масиву за допомогою reduce . Пропоную вам самим це спробувати.

    Фільтрування масивів Отже, є функція map для виконання операцій над кожним елементом масиву. Існує функція reduce, яка дозволяє стиснути масив до єдиного значення. Але що, якщо витягти з масиву лише деякі його елементи? Для того, щоб цю ідею досліджувати, розширимо список супергероїв, додамо деякі додаткові дані:

    Const heroes = [ (name: "Hulk", strength: 90000, sex: "m"), (name: "Spider-Man", strength: 25000, sex: "m"), (name: "Hawk Eye", strength: 136, sex: "m"), (name: "Thor", strength: 100000, sex: "m"), (name: "Black Widow", strength: 136, sex: "f"), (name : "Vision", strength: 5000, sex: "m"), (name: "Scarlet Witch", strength: 60, sex: "f"), (name: "Mystique", strength: 120, sex: "f "), (name: "Namora", strength: 75000, sex: "f"),];
    Тепер припустимо, що є дві задачі:

  • Знайти всіх героїв-жінок.
  • Знайти всіх героїв, сили яких перевищують 500.
  • До вирішення цих завдань можна підійти, використовуючи старий добрий цикл for…of:

    Let femaleHeroes = ; for (let hero of heroes) ( if (hero.sex === "f") ( femaleHeroes.push(hero); ) ) let superhumans = ; for (let hero of heroes) ( if (hero.strength >= 500) ( superhumans.push(hero); ) )
    Загалом виглядає це цілком пристойно. Але тут неозброєним оком видно повторюваний шаблон. Насправді, цикли абсолютно однакові, вони відрізняються лише блоками if . Що якщо винести ці блоки функції?

    Function isFemaleHero(hero) ( return (hero.sex === "f"); ) function isSuperhuman(hero) ( return (hero.strength >= 500); ) let femaleHeroes = ; for (let hero of heroes) ( if(isFemaleHero(hero)) ( femaleHeroes.push(hero); ) ) let superhumans = ; for (let hero of heroes) ( if (isSuperhuman(hero)) ( superhumans.push(hero); ) )
    Функції, які повертають тільки true або false, іноді називають предикатами. Ми використовуємо предикат для ухвалення рішення про те, чи треба зберегти в новому масиві чергове значення з масиву heroes.

    Те, як ми переписали код, зробило його довшим. Але, після виділення функцій-предикатів, стало краще видно ділянки програми, що повторюються. Створимо функцію, яка дозволить цих повторень позбутися:

    Function filter(predicate, arr) ( let working = ; for (let item of arr) ( if (predicate(item)) ( working = working.concat(item); ) ) return working; ) const femaleHeroes = filtr(isFemaleHero, heroes); const superhumans = filter(isSuperhuman, heroes);
    Тут, як і у випадку з вбудованими функціями map і reduce, JavaScript має те ж саме, що ми тут написали, у вигляді стандартного методу filter об'єкта Array. Тому писати власну функцію, без потреби, не потрібно. З використанням стандартних засобів код виглядатиме так:

    Const femaleHeroes = heroes.filter(isFemaleHero); const superhumans = heroes.filter(isSuperhuman);
    Чому такий підхід набагато кращий, ніж використання циклу for…of? Подумайте про те, як це можна скористатися на практиці. Ми маємо завдання виду: «Знайти всіх героїв, які…». Як тільки з'ясувалося, що можна вирішити задачу з використанням стандартної функції filter, робота спрощується. Все, що потрібно – повідомити цю функцію, які саме елементи нас цікавлять. Робиться це через написання однієї компактної функції. Не потрібно дбати ні про обробку масивів, ні про додаткові змінні. Натомість ми пишемо крихітну функцію-предикат і завдання вирішене.

    І, як і у випадку з іншими функціями, які працюють з масивами, використання filter дозволяє виразити більше інформації у меншому обсязі коду. Не потрібно читати весь стандартний код циклу для того, щоб розібратися в тому, що ми фільтруємо. Натомість все, що потрібно зрозуміти, описано прямо при виклику методу.

    Пошук у масивах Фільтрація – дуже корисна операція. Але що, якщо треба знайти лише одного супергероя зі списку? Скажімо, нас цікавить Black Widow. Функцію filter можна використовувати для вирішення цього завдання:

    Function isBlackWidow(hero) ( return (hero.name === "Black Widow"); ) const blackWidow = heroes.filter(isBlackWidow);
    Головна проблема полягає в тому, що подібне рішення не відрізняється ефективністю. Метод filter переглядає кожен елемент масиву. Однак, відомо, що в масиві лише одного героя звуть Black Widow, а це означає, що можна зупинитися після того, як його знайшли. У той же час, функціями-предикат зручно користуватися. Тому напишемо функцію find , яка знайде та поверне перший відповідний елемент:

    Function find(predicate, arr) ( for (let item of arr) ( if (predicate(item)) ( return item; ) ) ) const blackWidow = find(isBlackWidow, heroes);
    Тут, знову ж таки, треба сказати, що в JavaScript є вбудована функція, яка робить точно те, що потрібно:

    Const blackWidow = heroes.find(isBlackWidow);
    У результаті, як і раніше, вдалося висловити нашу ідею більш стисло. З використанням вбудованої функції find , завдання пошуку певного елемента зводиться до одного питання: «За якою ознакою можна визначити, що виявлений елемент?» При цьому не треба турбуватися про деталі.

    Читачі помітили, що неефективно двічі проходитись за списком героїв у наведених вище прикладах до функцій reduce і filter . Використання оператора розширення (spread operator) із ES2015 дозволяє зручно скомбінувати дві функції, зайняті згорткою масиву, в одну. Ось змінений фрагмент коду, який дозволяє проходити масивом лише один раз:

    Function processStrength((strongestHero, combinedStrength), hero) ( return ( strongestHero: greaterStrength(strongestHero, hero), combinedStrength: addStrength(combinedStrength, hero), ); ) const (strest th, (strongestHero : (strength: 0), з'єднанийStrength: 0));
    Не можу не помітити, що ця версія буде трохи складніша, ніж та, в якій по масиву проходили двічі, але, якщо масив величезний, скорочення числа проходів по ньому може бути дуже доречним. У будь-якому випадку порядок складності алгоритму залишається O(n).

    Підсумки Вважаю, представлені тут функції – це чудовий приклад того, чому продумано вибрані абстракції та користь приносять, і у коді виглядають добре. Припустимо, що ми використовуємо вбудовані функції скрізь, де це можливо. У кожному випадку виходить таке:
  • Ми позбавляємося циклів, що робить код стислішим і, швидше за все, легшим для читання.
  • Шаблон, що використовується, описується з використанням відповідного імені стандартного методу. Тобто - map, reduce, filter, або find.
  • Масштаб завдання зменшується. Замість самостійного написання коду для обробки масиву потрібно лише вказати стандартної функції на те, що треба з масивом зробити.
  • Зверніть увагу на те, що у кожному випадку для вирішення задачі використовуються компактні чисті функції.

    Насправді, якщо про все це замислитися, можна дійти висновку, який у перший момент здається дивовижним. Виявляється, якщо використовувати лише чотири вищеописані шаблони обробки масивів, можна прибрати з JS-коду практично всі цикли. Адже що робиться практично в кожному циклі, написаному на JavaScript? У ньому або обробляється, або конструюється якийсь масив, або робиться і те й інше. Крім того, у JS є інші стандартні функції для роботи з масивами, ви цілком можете вивчити їх самостійно. Позбавлення циклів практично завжди дозволяє зменшити складність програм і писати код, який легше читати і підтримувати.

    Шановні JavaScript-розробники, а у вас на прикметі є стандартні функції, які дозволяють покращити код, позбавившись якихось поширених «самописних» конструкцій?

    Теги: Додати теги