Замикання - javascript, mdn
Замикання - це функції, що посилаються на незалежні (вільні) змінні. Іншими словами, функція, певна в замиканні, «запам'ятовує» оточення, в якому вона була створена.
Лексична область видимості
Розглянемо наступний приклад:
Функція init () створює локальну змінну name. а потім викликає функцію displayName (). displayName () - це внутрішня функція - вона визначена всередині init () і доступна тільки всередині тіла цієї функції. На відміну від init (). displayName () не має локальних змінних і замість цього використовує змінну name. певну в батьківській функції.
Розглянемо наступний приклад:
У простому випадку, локальні змінні в функції існують тільки під час її виконання. Після виклику makeFunc () можна очікувати, що змінна name більше не буде доступна. Це, очевидно, не випадок замикання.
Вирішення цієї головоломки в тому, що myFunc стала замиканням. Замикання - це особливий вид об'єкта, який поєднує дві речі: функцію і оточення, в якому функція була створена. Оточення складається з будь-якої локальної змінної, яка була в області дії функції під час створення замикання. В цьому випадку, myFunc - це замикання, яке містить і функцію displayName. і рядок "Mozilla", які існували під час створення замикання.
Ось більш цікавий приклад - функція makeAdder:
Тут ми визначили функцію makeAdder (x). яка отримує x. і повертає нову функцію. Ця функція отримує y. і повертає суму x і y.
По суті, makeAdder це фабрична функція - вона створює функції, які можуть додавати специфічні значення для своїх аргументів. В наведеному вище прикладі ми використовуємо нашу фабричну функцію для створення двох нових функцій - одна визначає в якості свого аргументу значення 5, друга - 10.
add5 і add10 - це приклади замикань. Тіло цих функцій однаково, але при цьому вони зберігають різне оточення. В оточенні функції add5 x - це 5, в той час як в оточенні add10 x - це 10.
Замикання на практиці
Це все добре, але наскільки замикання корисні на практиці? Давайте подивимося, що з ними можна зробити. Взагалі, замикання дозволяє зв'язати якісь дані (конкретне оточення) з функцією, яка працює з цими даними. Очевидна паралель з об'єктно-орієнтованим програмуванням, де об'єкт дозволяє нам зв'язати між собою набір даних (властивості об'єкта) з одним або декількома методами.
Тобто, замикання можна використовувати всюди, де було б нормальним і правильним використовувати об'єкт з одним єдиним методом.
Давайте розглянемо практичний приклад: припустимо, ми хочемо додати на сторінку кілька кнопок, які будуть змінювати розмір тексту. Як варіант, ми можемо вказати властивість font-size на елементі body в пікселах, а потім встановлювати розмір інших елементів сторінки (таких, як заголовки, наприклад) з використанням відносних одиниць em:
Тоді наші кнопки мінятимуть властивість font-size елемента body, а інші елементи сторінки просто підчеплять це нове значення і відмасштабуйте розмір тексту завдяки використанню відносних одиниць.
Тепер size12. size14. і size16 - це функції, які змінюють розмір тексту в елементі body на значення 12, 14, і 16 пікселів, відповідно. Після чого ми чіпляємо ці функції на кнопки приблизно так:
Емуляція приватних методів за допомогою замикань
Мови начебто Java дозволяють нам оголошувати методи приватними. Це коли їх можуть викликати тільки інші методи того ж класу, а зовні вони не доступні.
Ось як можна описати за допомогою замикань кілька публічних методів, які мають доступ до приватних методів і змінним. Така манера програмування ще називається module pattern. в українській мові усталеного перекладу досі немає, але можна використовувати пошук слів "модуль", "шаблон" і "Javacript":
Тут багато чого змінилося. У попередньому прикладі кожне замикання мало свій власний контекст виконання (оточення). Тут ми створюємо єдиний оточення для трьох функцій: Counter.increment. Counter.decrement. і Counter.value.
Єдине оточення створюється в тілі анонімної функції, яка виконується в момент опису. Це оточення містить два приватних елемента: змінну privateCounter і фукцией changeBy. Жоден з цих елементів не доступний безпосередньо, за межами цієї самої анонімної функції. Замість цього вони можуть і повинні використовуватися трьома публічними функціями, які повертаються анонімним блоком коду (anonymous wrapper), виконуваних в тій же анонімної функції.
Зауважте, ми описуємо анонімну фунцию, що створює лічильник, і тут же запускаємо її, привласнюючи результат виконання змінної Counter. Але ми також можемо не запускати цю функцію відразу, а зберегти її в окремій змінної щоб використовувати для подальшого створення декількох лічильників ось так:
Зауважте, що лічильники працюють незалежно один від одного. Це відбувається тому, що у кожного з них в момент створення функцією makeCounter () також створювався свій окремий контекст виконання (оточення). Тобто приватна змінна privateCounter в кожному з лічильників це дійсно окрема самостійна змінна.
Використовуючи замикання подібним чином, ви отримуєте ряд переваг, зазвичай асоціюються з об'єктно-орієнтованим програмуванням, таких як ізоляція і інкапсуляція.
Створення замикань в циклі: Дуже часта помилка.
До того, як у версії ECMAScript 6 ввели ключове слово let. постійно виникала наступна проблема при створенні замикань всередині циклу. Розглянемо наступний приклад:
Масив helpText описує три підказки для трьох полів введення. Цикл пробігає ці описи по черзі і для кожного з полів введення визначає, що при виникненні події onfocus для цього елемента повинна викликатися функція, що показує відповідну підказку.
Якщо ви запустите цей код, то побачите, що він працює не так, як ми хотіли. Яке поле ви б не вибрали, як підказка завжди буде висвітлюватися повідомлення щодо віку.
Проблема в тому, що функції, присвоєні як обробники події onfocus, є замиканнями. Вони складаються з опису функції і контексту виконання (оточення), успадкованого від функції setupHelp. Було створено три замикання, але всі вони були створені з одним і тим же контекстом виконання (оточенням). До моменту виникнення події onfocus цикл вже давно відпрацював, а значить змінна item (одна і та ж для всіх трьох замикань) вказує на останній елемент масиву, який як раз про поле віку.
В якості вирішення в цьому випадку можна запропонувати використання функції, фабричної функції (function factory), як уже було описано вище в прикладах:
Ось це працює як слід. Замість того, щоб ділити на всіх одне оточення, функція makeHelpCallback створює кожному з замикань своє власне, в якому змінна item вказує на правильний елемент масиву helpText.
Міркування по продуктивності
Не потрібно без необхідності створювати функції всередині функцій в тих випадках, коли замикання не потрібні. Використання цієї техніки збільшує вимоги до продуктивності як в частині швидкості, так і в частині споживання пам'яті.
Як приклад, при створенні нового об'єкта / класу є сенс поміщати всі методи в прототип цього об'єкта, а не описувати їх в тексті конструктора. Справа в тому, що якщо зробити по-іншому, то при кожному створенні об'єкта для нього буде створено свій екземпляр кожного з методів, замість того, щоб наслідувати їх з прототипу.
Давайте розглянемо не надто практичний, але показовий приклад:
Оскільки вищенаведений код ніяк не використовує переваги замикань, його можна переписати таким чином:
Методи винесені в прототип. Проте, перевизначати прототип - саме по собі є поганою звичкою, тому давайте перепишемо все так, щоб нові методи просто додалися до вже існуючого прототипу.
Код вище можна зробити акуратніше, видаючи той же результат:
Дякуємо! Будь ласка, перевірте свою папку "Вхідні" для підтвердження підписки.
Якщо ви раніше не підтверджували підписку на розсилку новин Mozilla, то вам, можливо, доведеться зробити це. Будь ласка, перевірте папку Вхідні або Спам в своїй поштовій скриньці, щоб подивитися, чи не настав від нас лист.
Приховати підписку на розсилку новин
Чому MDN виглядає інакше?
MDN змінюється, фокусуючись тільки на документуванні веб-технологій. Все той же відмінний контент залишиться тут; ми міняємо тільки візуальні елементи і навігацію, щоб допомогти вам швидше знаходити документацію по веб-технологіям.
Але не хвилюйтеся, MDN і Mozilla як і раніше разом. За фактом, ми оновлюємо тільки оформлення MDN, щоб відповідати новому лого і квітам Mozilla.
Прочитайте більше про редизайн в пості на нашому блозі. Дякуємо за використання MDN!
Приховати повідомлення про редизайн