Js оператори завершення функції. JavaScript функції. Функції та побічні ефекти

Оператори переходу та обробка винятків

Ще однією категорією операторів JavaScript є оператори переходу. Як випливає із назви, ці оператори змушують інтерпретатор JavaScript переходити в інше місце у програмному коді. Оператор break змушує інтерпретатор перейти до кінця циклу чи іншої інструкції. Оператор continue змушує інтерпретатор пропустити частину тіла циклу, що залишилася, перейти назад в початок циклу і приступити до виконання нової ітерації. У мові JavaScript є можливість позначати інструкції іменами, завдяки чому в операторах break і continue можна явно вказувати, до якого циклу чи якоїсь інструкції вони належать.

Оператор return змушує інтерпретатор перейти із викликаної функції назад у точку її виклику та повернути значення виклику. Оператор throw збуджує виняток і призначений для роботи у поєднанні з операторами try/catch/finally, які визначають блок програмного коду обробки виключення. Це досить складний різновид операторів переходу: при появі виключення інтерпретатор переходить до найближчого об'ємного оброблювача винятків, який може перебувати в тій же функції або вище, у стеку повернення викликаної функції.

Докладніше всі ці оператори переходу описуються в наступних підрозділах.

Мітки інструкцій

Будь-яка інструкція може бути позначена вказаним перед нею ідентифікатором та двокрапкою:

ідентифікатор: інструкція

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

Надавши ім'я циклу, його потім можна використовувати в інструкціях break і continue, всередині циклу для виходу з нього або для початку циклу, до наступної ітерації. Інструкції break і continue є єдиними інструкціями в мові JavaScript, в яких можна вказувати мітки – про них детальніше розповідається далі. Нижче наведено приклад інструкції while з міткою та інструкції continue, яка використовує цю мітку:

Mainloop: while (token != null) ( // Програмний код опущений... continue mainloop; // Перехід до наступної ітерації іменованого циклу )

Ідентифікатор, який використовується як мітка інструкції, може бути будь-яким допустимим ідентифікатором JavaScript, крім зарезервованого слова. Імена міток відокремлені від імен змінних і функцій, тому як мітки допускається використовувати ідентифікатори, що збігаються з іменами змінних або функцій.

Мітки інструкцій визначені лише всередині інструкцій, до яких вони застосовуються (і, звичайно ж, всередині інструкцій, що вкладені в них). Вкладені інструкції не можуть позначатися тими ж ідентифікаторами, що і інструкції, що вміщують їх, але дві незалежні інструкції можуть позначатися однаковими мітками. Позначені вказівки можуть позначатися повторно. Тобто будь-яка інструкція може мати безліч міток.

Оператор break

Оператор break призводить до негайного виходу з внутрішнього циклу або оператора switch. Раніше ми вже бачили приклади використання оператора break усередині оператора switch. У циклах він зазвичай використовується для негайного виходу з циклу, коли з якихось причин потрібно завершити виконання циклу.

Коли цикл має дуже складну умову завершення, найчастіше простіше реалізувати ці умови з допомогою оператора break, ніж намагатися висловити в одному умовному вираженні циклу. Наступний приклад намагається знайти елемент масиву з певним значенням. Цикл завершується звичайним чином після досягнення кінця масиву або за допомогою оператора break, як тільки буде знайдено потрібне значення:

Var arr = ["а", "б", "в", "г", "д"], result; for (var i = 0; i

У мові JavaScript допускається вказувати ім'я мітки за ключовим словом break (ідентифікатор без двокрапки):

break имя_метки;

Коли оператор break використовується з міткою, вона виконує перехід до кінця іменованої інструкції або припинення її виконання. У разі відсутності інструкції із зазначеною міткою спроба використовувати таку форму оператора break породжує синтаксичну помилку. Іменована інструкція має бути циклом чи оператором switch. Оператор break з міткою може виконувати «вихід» з будь-якої інструкції, що вміщає її. Об'ємна інструкція може бути навіть простим блоком інструкцій, укладеним у фігурні дужки виключно з метою помітити його.

Між ключовим словом break та ім'ям мітки не дозволяється вставляти символ перекладу рядка. Справа в тому, що інтерпретатор JavaScript автоматично вставляє пропущені точки з комою: якщо розбити рядок програмного коду між ключовим словом break і наступною за ним міткою, інтерпретатор припустить, що мала на увазі проста форма цього оператора без мітки, і додасть крапку з комою.

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

Оператор continue

Оператор continue схожий на оператора break. Однак замість виходу із циклу оператор continue запускає нову ітерацію циклу. Синтаксис оператора continue так само простий, як і синтаксис оператора break. Оператор continue також може використовуватися з міткою.

Оператор continue як у формі без мітки, так і з міткою може використовуватися тільки в тілі циклу. Використання його в будь-яких інших місцях призводить до синтаксичної помилки. Коли виконується оператор continue, поточна ітерація циклу припиняється і починається наступна. Для різних типів циклів це означає різне:

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

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

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

    У циклі for/in цикл починається наново із присвоєнням зазначеної змінної імені наступної властивості.

Зверніть увагу на відмінності у поведінці оператора continue у циклах while та for. Цикл while повертається безпосередньо до своєї умови, а цикл for спочатку обчислює вираз інкременту, а потім повертається до умови. У наступному прикладі показано використання оператора continue без мітки для виходу з поточної ітерації циклу для парних чисел:

Var sum = 0; // Обчислити суму парних чисел від 0 - 10 for (var i = 0; i

Оператор continue, як і break, може застосовуватися у вкладених циклах у формі, що включає мітку, і тоді циклом, що заново запускається, необов'язково буде цикл, що безпосередньо містить оператор continue. Крім того, як і для break, переклади рядків між ключовим словом continue та ім'ям мітки не допускаються.

Оператор return

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

Якщо функція не має оператора return, при її викликі інтерпретатор виконуватиме інструкції в тілі функції одну за одною, поки не досягне кінця функції, і потім поверне керування програмою, що викликала її. У цьому випадку вираз виклику поверне значення undefined. Оператор return часто є останньою інструкцією в функції, але це зовсім необов'язково: функція поверне управління програмі, що викликає, як тільки буде досягнутий оператор return, навіть якщо за ним слідують інші інструкції в тілі функції.

Оператор return може також використовуватися без виразу, тоді вона просто перериває виконання функції і повертає значення undefined зухвалої програми. Наприклад:

Function myFun(arr) ( // Якщо масив містить запереч. числа, перервати функцію for (var i = 0; i

Оператор throw

Виняток - це сигнал, що вказує на виникнення якоїсь виняткової ситуації чи помилки. Порушення виключення (throw)- це спосіб просигналізувати про таку помилку або виняткову ситуацію. Перехопити виняток (catch) - отже обробити його, тобто. вжити заходів, необхідних або підходящих для відновлення після виключення.

У JavaScript винятки збуджуються у випадках, коли виникає помилка часу виконання і коли програма явно збуджує його з допомогою оператора throw. Винятки перехоплюються за допомогою операторів try/catch/finally, які описуються пізніше.

Оператор throw має наступний синтаксис:

throw вираз;

Результатом виразу може бути значення будь-якого типу. Оператору throw можна передати число, що представляє код помилки або рядок, що містить текст повідомлення про помилку. Інтерпретатор JavaScript збуджує винятки, використовуючи екземпляр класу Error одного з його підкласів, і ви також можете використати подібний підхід. Об'єкт Error має властивість name, що визначає тип помилки, та властивість message, Що містить рядок, передану функції-конструктору Нижче наведено приклад функції, яка збуджує об'єкт Error при виклику з неприпустимим аргументом:

// Функція факторіалу числа function factorial(number) ( // Якщо вхідний аргумент не є допустимим значенням, // збуджується виняток! if (number 1; i *= number, number--); /* порожнє тіло циклу */ return i ; ) console.log("5! = ", factorial(5)); console.log("-3! = ", factorial(-3));

Коли збуджується виняток, інтерпретатор JavaScript негайно перериває нормальне виконання програми та переходить до найближчого обробника винятків. У обробниках винятків використовується оператор catch конструкції try/catch/finally, опис якої наведено у наступному розділі.

Якщо блок програмного коду, в якому виник виняток, не має відповідної конструкції catch, інтерпретатор аналізує зовнішній блок програмного коду і перевіряє, чи пов'язаний з ним обробник винятків. Це триває доти, доки обробник не буде знайдено.

Якщо виняток генерується функції, що не містить конструкції try/catch/finally, призначеної для його обробки, то виняток поширюється вище, програмний код, що викликав функцію. Таким чином, виключення поширюються по лексичній структурі методів JavaScript вгору по стеку викликів. Якщо обробник виключення так і не буде знайдений, виняток розглядається як помилка і повідомляється користувачеві.

Конструкція try/catch/finally

Конструкція try/catch/finally реалізує механізм обробки винятків JavaScript. Оператор try у цій конструкції просто визначає блок коду, в якому обробляються винятки. За блоком try слід оператор catch з блоком інструкцій, що викликаються, якщо де-небудь у блоці try виникає виняток. За оператором catch слід блок finally містить програмний код, що виконує заключні операції, який гарантовано виконується незалежно від того, що відбувається в блоці try.

І блок catch, і блок finally не є обов'язковими, однак після блоку try повинен бути присутнім хоча б один з них. Блоки try, catch та finally починаються і закінчуються фігурними дужками. Це обов'язкова частина синтаксису і вона не може бути опущена, навіть якщо між ними міститься тільки одна інструкція.

Наступний фрагмент ілюструє синтаксис та призначення конструкції try/catch/finally:

Try (// Зазвичай цей код без збоїв працює від початку до кінця. // Але в якийсь момент у ньому може бути згенеровано виняток // або безпосередньо за допомогою оператора throw, або побічно - // викликом методу, що генерує виняток.) catch (ex) ( // Інструкції в цьому блоці виконуються тоді і тільки тоді, коли в блоці try // виникає виняток. Ці інструкції можуть використовувати локальну змінну ex, // посилається на об'єкт Error або інше значення, вказане в операторі throw. // Цей блок може або деяким чином обробити виняток, або // проігнорувати його, роблячи щось інше, або заново згенерувати // виняток за допомогою оператора throw.) finally ( // Цей блок містить інструкції, які виконуються завжди, незалежно від того , // що відбулося в блоці try.Вони виконуються, якщо блок try завершився: // 1) як звичайно, досягнувши кінця блоку // 2) через операторів break, continue або return // 3) з винятком, обробленим наведеним в блоці catch вище // 4) з неперехопленим винятком, яке продовжує своє // поширення більш високі рівні )

Зауважте, що за ключовим словом catch слід ідентифікатор у дужках. Цей ідентифікатор нагадує параметр функції. Коли буде перехоплено виняток, цей параметр буде присвоєно виняток (наприклад, об'єкт Error). На відміну від звичайної змінної ідентифікатор, асоційований з оператором catch, існує лише у тілі блоку catch.

Далі наводиться реалістичніший приклад конструкції try/catch. У ньому викликаються метод factorial(), визначений у попередньому прикладі, та методи prompt() та alert() клієнтського JavaScript для організації введення та виведення:

Try ( // Запросити число у користувача var n = Number(prompt("Введіть позитивне число", "")); // Обчислити факторіал числа, припускаючи, // що вхідні дані коректні var f = factorial(n); // Вивести результат alert(n + "! = " + f); ) catch (ex) ( // Якщо дані некоректні, управління буде передано сюди alert(ex); // Повідомити користувача про помилку )

Якщо користувач введе від'ємне число, з'явиться попереджувальне повідомлення:

Це приклад конструкції try/catch без оператора finally. Хоча finally використовується не так часто, як catch, проте іноді цей оператор виявляється корисним. Блок finally гарантовано виконується, якщо виконувалася хоча б якась частина блоку try незалежно від того, яким чином завершилося виконання програмного коду в блоці try. Ця можливість зазвичай використовується для виконання заключних операцій після виконання програмного коду протягом try.

У звичайній ситуації керування доходить до кінця блоку try, а потім переходить до блоку finally, який виконує необхідні заключні операції. Якщо управління вийшло з блоку try як результат виконання операторів return, continue або break, перед передачею управління інше місце виконується блок finally.

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

Якщо сам блок finally передає керування за допомогою операторів return, continue, break або throw або шляхом виклику методу, що генерує виняток, незакінчена команда на передачу керування скасовується та виконується нова. Наприклад, якщо блок finally згенерує виняток, цей виняток замінить будь-який раніше згенерований виняток.

Люди вважають, що комп'ютерні науки – це мистецтво геніїв. Насправді все навпаки - просто багато людей роблять речі, які стоять один на одному, ніби складаючи стіну з маленьких камінчиків.

Дональд Батіг

Ви вже бачили виклики таких функцій, як alert . Функції – це хліб з олією програмування JavaScript. Ідея обертання шматка програми та виклику її як змінної дуже затребувана. Це інструмент для структурування великих програм, зменшення повторень, призначення імен підпрограм та ізолювання підпрограм один від одного.

Найочевидніше використання функцій – створення словника. Вигадувати слова для звичайної людської прози – поганий тон. У мові програмування це потрібно.

Середня доросла російськомовна людина знає приблизно 10000 слів. Рідкісна мова програмування містить 10000 вбудованих команд. І словник мови програмування визначений чіткіше, тому менш гнучкий, ніж людський. Тому нам зазвичай доводиться додавати до нього свої слова, щоб уникнути зайвих повторень.

Визначення функції Визначення функції – звичайне визначення змінної, де значення, яке отримує змінна, є функцією. Наприклад, наступний код визначає змінну square, яка посилається на функцію, яка підраховує квадрат заданого числа:

Var square = function(x) ( return x * x; ); console.log(square(12)); // → 144

Функція створюється виразом, що починається з ключового слова function. Функції мають набір параметрів (в даному випадку тільки x), і тіло, що містить інструкції, які необхідно виконати при виклику функції. Тіло функції завжди укладають у фігурні дужки, навіть якщо воно складається з однієї інструкції.

Функція може мати кілька параметрів, або взагалі їх не бути. У наступному прикладі makeNoise не має списку параметрів, а у power їх цілих два:

Var makeNoise = function() ( console.log("Хрячись!"); ); makeNoise(); // → Хрясь! var power = function(base, exponent) ( var result = 1; for (var count = 0; count< exponent; count++) result *= base; return result; }; console.log(power(2, 10)); // → 1024

Деякі функції повертають значення, як power і square, інші не повертають, як makeNoise, яка справляє лише побічний ефект. Інструкція return визначає значення, яке повертається функцією. Коли обробка програми доходить до цієї інструкції, вона відразу виходить з функції, і повертає це значення в місце коду, звідки була викликана функція. return без виразу повертає значення undefined.

Параметри та область видимості Параметри функції – такі ж змінні, але їх початкові значення задаються під час виклику функції, а не коду.

Важлива властивість функцій у тому, що змінні, створені всередині функції (включно з параметрами), локальні всередині цієї функції. Це означає, що у прикладі з power змінна result буде створюватися щоразу під час виклику функції, і ці окремі її інкарнації жодним чином друг з одним не пов'язані.

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

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

Var x = "outside"; var f1 = function() ( var x = "inside f1"; ); f1(); console.log(x); // → outside var f2 = function() ( x = "inside f2"; ); f2(); console.log(x); // → inside f2

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

Вкладені області видимості JavaScript розрізняє не тільки глобальні та локальні змінні. Функції можна встановити всередині функцій, що призводить до кількох рівнів локальності.

Наприклад, наступна досить безглузда функція містить усередині ще дві:

Var landscape = function() ( var result = ""; var flat = function(size) ( for (var count = 0; count)< size; count++) result += "_"; }; var mountain = function(size) { result += "/"; for (var count = 0; count < size; count++) result += """; result += "\\"; }; flat(3); mountain(4); flat(6); mountain(1); flat(1); return result; }; console.log(landscape()); // → ___/""""\______/"\_

Функції flat і mountain бачать змінну result, тому що вони знаходяться всередині функції, в якій вона визначена. Але вони не можуть бачити змінні count один одного, тому що змінні однієї функції знаходяться поза межами видимості іншої. А оточення зовні функції landscape не бачить жодної із змінних, визначених усередині цієї функції.

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

Люди, які вивчали інші мови програмування, можуть подумати, що будь-який блок, ув'язнений у фігурні дужки, створює своє локальне оточення. Але в JavaScript область видимості створюють лише функції. Ви можете використовувати окремі блоки:

Var деякий = 1; (var something = 2; // Робимо щось зі змінною something...) // Вийшли з блоку...

Але деякі всередині блоку – це та ж змінна, що й зовні. Хоча такі блоки і дозволені, варто використовувати їх тільки для команди if і циклів.

Якщо це видається вам дивним – так здається не тільки вам. У версії JavaScript 1.7 з'явилося ключове слово let, яке працює як var, але створює змінні, локальні для будь-якого блоку, а не тільки для функції.

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

Але це дві різні речі. Виклик функції можна використовувати як просту змінну – наприклад, використовувати їх у будь-яких виразах. Можливо зберігати виклик функції в новій змінній, передавати її як параметр іншої функції тощо. Також змінна, що зберігає виклик функції, залишається нормальної змінної і його значення можна поменять:

Var launchMissiles = function(value) ( ​​missileSystem.launch("плі!"); ); if (safeMode) launchMissiles = function(value) (/* відбій */);

У розділі 5 ми обговоримо чудові речі, які можна зробити, передаючи виклики функцій іншим функціям.

Оголошення функцій Є коротка версія виразу “var square = function…”. Ключове слово function можна використовувати на початку інструкції:

Function square(x) ( return x * x; )

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

Console.log("The future says:", future()); function future() ( return "We STILL have no flying cars."; )

Такий код працює, хоча функція оголошується нижче за той код, який її використовує. Це відбувається тому, що оголошення функцій є частиною звичайного виконання програм зверху вниз. Вони «переміщаються» до їхньої області видимості і можуть бути викликані в будь-якому коді в цій області. Іноді це зручно, тому що ви можете писати код в такому порядку, який виглядає найбільш осмислено, не турбуючись про необхідність визначати всі функції вище того місця, де вони використовуються.

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

Function example() ( function a() () // Нормуль if (something) ( function b() () // Ай-яй-яй! ) )

Стек дзвінків Корисним буде придивитися до того, як порядок роботи працює з функціями. Ось проста програма з кількома викликами функцій:

Function greet(who) ( console.log("Привіт, "+ who); ) greet("Семен"); console.log("Покида");

Обробляється вона приблизно так: виклик greet змушує прохід стрибнути на початок функції. Він викликає вбудовану функцію console.log, яка перехоплює контроль, робить свою справу та повертає контроль. Потім він доходить до кінця greet і повертається до місця, звідки його викликали. Наступний рядок знову викликає console.log.

Схематично це можна показати так:

Top greet console.log greet top console.log top

Оскільки функція повинна повернутися на місце, звідки її викликали, комп'ютер повинен запам'ятати контекст, з якого була викликана функція. В одному випадку, console.log має повернутися назад у greet. В іншому вона повертається в кінець програми.

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

Зберігання стека вимагає місця у пам'яті. Коли стек занадто сильно розростається, комп'ютер припиняє виконання і видає щось на кшталт stack overflow або too much recursion. Наступний код це демонструє – він ставить комп'ютеру дуже складне питання, що призводить до нескінченних стрибків між двома функціями. Точніше, це були б нескінченні стрибки, якби комп'ютер мав нескінченний стек. Насправді стек переповнюється.

Function chicken() ( return egg(); ) function egg() ( return chicken(); ) console.log(chicken() + "came first."); // → ??

Необов'язкові аргументи Наступний код цілком дозволений і виконується без проблем:

Alert("Здрастуйте", "Добрий вечір", "Всім привіт!");

Офіційно функція набуває одного аргументу. Однак, за такого виклику вона не скаржиться. Вона ігнорує решту аргументів і показує «Здрастуйте».

JavaScript дуже лояльний щодо кількості аргументів, що передаються функції. Якщо ви передасте занадто багато, зайві будуть проігноровані. Занадто мало – відсутнім буде призначено значення undefined.

Мінус цього підходу в тому, що можливо, і навіть ймовірно, передати функції неправильну кількість аргументів, і вам ніхто на це не поскаржиться.

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

Function power(base, exponent) ( if (exponent == undefined) exponent = 2; var result = 1; for (var count = 0; count< exponent; count++) result *= base; return result; } console.log(power(4)); // → 16 console.log(power(4, 3)); // → 64

У наступному розділі побачимо, як у тілі функції можна дізнатися точну кількість переданих їй аргументів. Це корисно, т.к. дозволяє створювати функцію, яка приймає будь-яку кількість аргументів. Наприклад, console.log використовує цю властивість і виводить усі передані йому аргументи:

Console.log("R", 2, "D", 2); // → R 2 D 2

Замикання Можливість використовувати виклики функцій як змінні разом з тим фактом, що локальні змінні щоразу при виклику функції створюються заново, призводить до цікавого питання. Що відбувається з локальними змінними, коли функція перестає працювати?

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

Function wrapValue(n) ( var localVariable = n; return function() ( return localVariable; ); ) var wrap1 = wrapValue(1); var wrap2 = wrapValue(2); console.log(wrap1()); // → 1 console.log(wrap2()); // → 2

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

Ця можливість працювати з посиланням на якийсь екземпляр локальної змінної називається замиканням. Функція, що замикає локальні змінні, називається замикаючою. Вона не лише звільняє вас від турбот, пов'язаних із часом життя змінних, а й дозволяє творчо використовувати функції.

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

Function multiplier(factor) ( return function(number) ( return number * factor; ); ) var twice = multiplier(2); console.log(twice(5)); // → 10

Окрема змінна на кшталт localVariable з прикладу з wrapValue вже не потрібна. Оскільки параметр – сам собою локальна змінна.

Потрібна практика, щоб почати мислити подібним чином. Хороший варіант уявної моделі - уявляти, що функція заморожує код у своєму тілі і обгортає його в упаковку. Коли ви бачите return function(...) (...), уявляйте, що це пульт керування шматком коду, замороженим для вживання пізніше.

У нашому прикладі multiplier повертає заморожений шматок коду, який ми зберігаємо у змінній twice. Останній рядок викликає функцію, що міститься в змінній, у зв'язку з чим активується збережений код (return number * factor;). У нього все ще є доступ до змінної factor, яка визначалася при виклику multiplier, до того ж має доступ до аргументу, переданого під час розморожування (5) в якості числового параметра.

Рекурсія Функція може викликати сама себе, якщо вона піклується про те, щоб не переповнити стек. Така функція називається рекурсивною. Ось приклад альтернативної реалізації зведення у ступінь:

Function power(base, exponent) ( if (exponent == 0) return 1; else return base * power(base, exponent - 1); ) console.log(power(2, 3)); // → 8

Приблизно так математики визначають зведення у ступінь, і, можливо, це описує концепцію елегантніше, ніж цикл. Функція викликає багато разів із різними аргументами задля досягнення багаторазового множення.

Однак, у такої реалізації є проблема – у звичайному середовищі JavaScript вона раз на 10 повільніша, ніж версія з циклом. Прохід циклу виходить дешевше, ніж виклик функції.

Дилема "швидкість проти елегантності" досить цікава. Є певний проміжок між зручністю для людини та зручністю для машини. Будь-яку програму можна прискорити, зробивши її більшою і мудрішою. Від програміста потрібно знаходити відповідний баланс.

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

Основне правило, яке вже не раз повторювали, і з яким я повністю згоден - не турбуйтеся про швидкодію, поки ви точно не впевнені, що програма гальмує. Якщо так, знайдіть ті частини, які працюють найдовше, і змінюйте там елегантність на ефективність.

Звичайно, ми не повинні відразу повністю ігнорувати швидкодію. У багатьох випадках, як зі зведенням у ступінь, особливої ​​простоти від елегантних рішень ми не отримуємо. Іноді досвідчений програміст одразу бачить, що простий підхід ніколи не буде досить швидким.

Я загострюю на цьому увагу тому, що занадто багато програмістів-початківців хапаються за ефективність навіть у дрібницях. Результат виходить більшим, складнішим і часто не без помилок. Такі програми довше писати, а працюють вони часто не дуже швидше.

Але рекурсія який завжди лише менш ефективна альтернатива циклам. Деякі завдання найпростіше вирішити рекурсією. Найчастіше це обхід кількох гілок дерева, кожна з яких може розгалужуватися.

Ось вам загадка: чи можна отримати нескінченну кількість чисел, починаючи з числа 1, і потім або додаючи 5, або помножуючи на 3. Як нам написати функцію, яка, отримавши число, намагається знайти послідовність таких додавань і множень, які призводять до заданого числа? Наприклад, число 13 можна отримати, спочатку помноживши 1 на 3, а потім додавши 5 рази. А число 15 взагалі не можна отримати.

Рекурсивне рішення:

Function findSolution(target) ( function find(start, history) ( if (start == target) return history; else if (start > target) return null; else return find(start + 5, "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)"); ) return find(1, "1"); ) console.log(findSolution(24)); // → (((1 * 3) + 5) * 3)

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

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

Для цього функція виконує одну з трьох дій. Якщо задане число дорівнює меті, то поточна історія таки є способом її досягнення, тому вона і повертається. Якщо задане число більше за мету, продовжувати множення і складання сенсу немає, тому що так воно тільки збільшуватиметься. А якщо ми ще не досягли мети, функція пробує обидва можливі шляхи, що починаються із заданого числа. Вона двічі викликає себе, один раз із кожним із способів. Якщо перший дзвінок повертається не null, він повертається. В іншому випадку повертається другий.

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

Find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5) ) + 5)") too big find(33, "(((1 + 5) + 5) * 3)") too big find(18, "((1 + 5) * 3)") too big find( 3, "(1 * 3)") find (8, "((1 * 3) + 5)") find (13, "(((1 * 3) + 5) + 5)") found!

Відступ показує глибину стека дзвінків. Вперше функція find викликає сама себе двічі, щоб перевірити рішення, що починаються з (1+5) та (1*3). Перший виклик шукає рішення, що починається з (1 + 5), і за допомогою рекурсії перевіряє всі рішення, що видають число, менше або дорівнює необхідному. Не знаходить і повертає null. Тоді оператор || та переходить до виклику функції, який досліджує варіант (1*3). Тут на нас чекає успіх, тому що в третьому рекурсивному викликі ми отримуємо 13. Цей виклик повертає рядок, і кожен із операторів || шляхом передає цей рядок вище, в результаті повертаючи рішення.

Вирощуємо функції Існує два більш-менш природні способи введення функцій у програму.

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

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

Те, наскільки складно підібрати ім'я для функції, показує, як добре ви уявляєте собі її функціональність. Візьмемо приклад. Нам потрібно написати програму, яка виводить два числа, кількість корів та курок на фермі, за якими йдуть слова «корів» та «куриць». До числа потрібно спереду додати нулі так, щоб кожне займало рівно три позиції.

007 Корів 011 Куриць

Очевидно, що нам знадобиться функція із двома аргументами. Починаємо кодувати.
// вивестиІнвентаризаціюФерми function printFarmInventory(cows, chickens) ( var cowString = String(cows); while (cowString.length< 3) cowString = "0" + cowString; console.log(cowString + " Коров"); var chickenString = String(chickens); while (chickenString.length < 3) chickenString = "0" + chickenString; console.log(chickenString + " Куриц"); } printFarmInventory(7, 11);

Якщо ми додамо до рядка.length, ми отримаємо її довжину. Виходить, що цикли while додають нулі спереду до числа, доки не отримають рядок в 3 символи.

Готово! Але тільки ми зібралися відправити фермеру код (разом із неабияким чеком, зрозуміло), він дзвонить і каже нам, що у нього в господарстві з'явилися свині, і чи не могли б ми додати до програми виведення кількості свиней?

Звичайно можна. Але коли ми починаємо копіювати та вставляти код із цих чотирьох рядків, ми розуміємо, що треба зупинитися і подумати. Має бути спосіб кращим. Намагаємось покращити програму:

// висновокДодаванняНулеюІміткою function printZeroPaddedWithLabel(number, label) ( var numberString = String(number); while (numberString.length< 3) numberString = "0" + numberString; console.log(numberString + " " + label); } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Коров"); printZeroPaddedWithLabel(chickens, "Куриц"); printZeroPaddedWithLabel(pigs, "Свиней"); } printFarmInventory(7, 11, 3);

Працює! Але назва printZeroPaddedWithLabel трохи дивна. Воно поєднує три речі – висновок, додавання нулів та мітку – в одну функцію. Замість того, щоб вставляти в функцію весь фрагмент, що повторюється, давайте виділимо одну концепцію:

// додатиНулі function zeroPad(number, width) ( var string = String(number); while (string.length< width) string = "0" + string; return string; } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { console.log(zeroPad(cows, 3) + " Коров"); console.log(zeroPad(chickens, 3) + " Куриц"); console.log(zeroPad(pigs, 3) + " Свиней"); } printFarmInventory(7, 16, 3);

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

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

Хороше правило - додавайте тільки ту функціональність, яка вам знадобиться. Іноді з'являється спокуса створювати фреймворки загального призначення кожної невеликої потреби. Опирайтеся йому. Ви ніколи не закінчите роботу, а просто напишете купу коду, який ніхто не використовуватиме.

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

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

Чиста функція - особливий вид функції, що повертає значення, яка не тільки не має побічних ефектів, а й не залежить від побічних ефектів решти коду - наприклад, не працює з глобальними змінними, які можуть бути випадково змінені деінде. Чиста функція, будучи викликаною з тими самими аргументами, повертає той самий результат (і більше нічого не робить) – що досить приємно. Із нею просто працювати. Виклик такої функції можна подумки замінювати результатом її роботи, без зміни значення коду. Коли ви хочете перевірити таку функцію, ви можете просто викликати її і бути впевненим, що якщо вона працює в даному контексті, вона працюватиме в будь-якому. Не такі чисті функції можуть повертати різні результати в залежності від багатьох факторів і мати побічні ефекти, які складно перевіряти та враховувати.

Однак, не треба соромитися писати не зовсім чисті функції, або починати священне чищення коду від таких функцій. Побічні ефекти часто є корисними. Немає способу написати чисту версію функції console.log, і ця функція дуже корисна. Деякі операції легко висловити, використовуючи побічні ефекти.

Підсумок Цей розділ показав, як писати власні функції. Коли ключове слово function використовується як вираз, повертає покажчик на виклик функції. Коли використовується як інструкція, ви можете оголошувати змінну, призначаючи їй виклик функції.

Ключовий момент у розумінні функцій – локальні зони видимості. Параметри та змінні, оголошені всередині функції, локальні для неї, перетворюються щоразу при її викликі, і не видно зовні. Функції, оголошені всередині іншої функції, мають доступом до її області видимості.

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

ВправиМинимум У попередньому розділі була згадана функція Math.min, що повертає найменший з аргументів. Тепер ми можемо написати цю функцію самі. Напишіть функцію min, яка приймає два аргументи, і повертає мінімальний із них.

Console.log(min(0, 10)); // → 0 console.log (min (0, -10)); // → -10

Рекурсія Ми бачили, що оператор % (залишок від розподілу) може використовуватися визначення того, чи парне число (% 2). А ось ще один спосіб визначення:

Нуль парний.
Одиниця непарна.
У будь-якого числа N парність така сама, як у N-2.

Напишіть рекурсивну функцію isEven відповідно до цих правил. Вона має приймати число та повертати булевське значення.

Потестуйте її на 50 та 75. Спробуйте задати їй -1. Чому вона поводиться таким чином? Чи можна її якось виправити?

Test it on 50 and 75. See how it behaves on -1. Why? Чи може ви думати про те, що це?

Console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → ??

Вважаємо боби.

Символ номер N рядка можна отримати, додавши до неї.charAt(N) (“рядок”.charAt(5)) – так само з отриманням довжини рядка за допомогою.length. Значення, що повертається буде рядковим, що складається з одного символу (наприклад, "к"). У першого символу рядка позиція 0, що означає, що у останнього символу позиція буде string.length – 1. Іншими словами, рядок з двох символів має довжину 2, а позиції її символів будуть 0 і 1.

Напишіть функцію countBs, яка приймає рядок як аргумент, та повертає кількість символів “B”, що містяться у рядку.

Потім напишіть функцію countChar, яка працює приблизно як countBs, тільки приймає другий параметр - символ, який ми шукатимемо в рядку (замість просто рахувати кількість символів “B”). Для цього перейдіть функцію countBs.

Функції є одним з найбільш важливих будівельних блоків коду JavaScript.

Функції складаються з набору команд і зазвичай виконують якусь одну певну задачу (наприклад, підсумовування чисел, обчислення кореня тощо).

Код, поміщений у функцію, буде виконано лише після явного виклику цієї функції.

Оголошення функцій

1. Синтаксис:

//Оголошення функції function ім'яФункції(пер1, пер2)( Код функції) //Виклик функції ім'яФункції(пер1,пер2);

2. Синтаксис:

// Оголошення функції var ім'я функції = function (пер1, пер2) (Код функції) // Виклик функції ім'я функції (пер1, пер2);

Ім'яфункції визначає ім'я функції. Кожна функція на сторінці має мати унікальне ім'я. Ім'я функції має бути задано латинськими літерами і починатися з цифр.

пер1 та пер2 є змінними або значеннями, які можна передавати всередину функції. У кожну функцію може бути передано необмежену кількість змінних.

Зверніть увагу: навіть якщо у функцію не передаються змінні, не забувайте вставляти круглі дужки "()" після імені функції.

Зверніть увагу: імена функцій JavaScript чутливі до регістру.

Приклад JavaScript функції

Функція messageWrite() у наведеному нижче прикладі буде виконана тільки після натискання на кнопку.

Зверніть увагу: у цьому прикладі використовується подія onclick. Події JavaScript будуть детально розглянуті далі у цьому підручнику.

// Функція виводить текст на сторінку function messageWrite() ( document.write("Цей текст був виведений на сторінку за допомогою JavaScript!"); )

Передача функцій змінних

Ви можете передавати функціям необмежену кількість змінних.

Зверніть увагу: всі маніпуляції над змінними всередині функцій насправді виконуються не над самими змінними, а над їх копією, тому вміст самих змінних у результаті виконання функцій не змінюється.

/* Задамо функцію, яка додає до переданої змінної 10 і виводить результат на сторінку */ function plus(a)( a=a+10; document.write("Виведення функції: " + a+"
"); ) var a=25; document.write("Значення змінної до виклику функції: "+a+"
"); // Викликаємо функцію передавши їй як аргумент змінну a plus(a); document.write("Значення змінної після виклику функції: "+a+"
");

Швидкий перегляд

Щоб звернутися до глобальної змінної з функції, а не її копії, використовуйте window.ім'я_змінної .

Function plus(a)( window.a=a+10; ) var a=25; document.write("Значення змінної до виклику функції: "+a+"
"); plus(a); document.write("Значення змінної після виклику функції: "+a+"
");

Швидкий перегляд

Команда return

За допомогою команди return можна повертати з функцій значення.

//Функція sum повертає суму переданих до неї змінних function sum(v1,v2)( return v1+v2; ) document.write("5+6=" + sum(5,6) + "
"); document.write("10+4=" + sum(10,4) + "
");

Швидкий перегляд

Вбудовані функції

Крім визначених користувачем функцій JavaScript існують ще й вбудовані функції .

Наприклад, вбудована функція isFinite дозволяє перевірити, чи є передане значення допустимим числом.

Document.write(isFinite(40)+"
"); document.write(isFinite(-590)+"
"); document.write(isFinite(90.33)+"
"); document.write(isFinite(NaN)+"
"); document.write(isFinite("Це рядок")+"
");

Швидкий перегляд

Зверніть увагу: повний список вбудованих функцій JavaScript Ви можете знайти у нашому .

Локальні та глобальні змінні

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

Після виконання коду функції такі змінні знищуються. Це означає, що у різних функціях можна визначити змінні з однаковим ім'ям.

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

Якщо Ви оголошуєте змінну без var всередині функції, вона теж стає глобальною.

Глобальні змінні знищуються лише після закриття сторінки.

//Оголосимо глобальні змінні var1 та var2 var var1="var1 існує"; var var2; function func1() ( //Привласним var2 значення всередині функції func1 var var2="var2 існує"; ) //З іншої функції виведемо вміст змінної var1 і var2 на сторінку function func2() ( //Виводимо вміст змінної var1 document.write( var1 + "
"); //Виводимо вміст змінної var2 document.write(var2); )

Швидкий перегляд

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

Використання анонімних функцій

Функції, які не містять імені під час оголошення називаються анонімними.

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

Function arrMap(arr,func)( var res=new Array; for (var i=0;i