Замикання в JavaScript: практичний приклад, особливості та правила

16 0 Новини високих технологій

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

Концепція замикань

[thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_491.jpeg[/thumb]
Закриття були розроблені в 1960-х роках для механічної оцінки виразів в обчисленні та застосовані в 1970 році як особливість мови програмування PAL для підтримки функцій першого класу з лексичної сферою. Пітер Ландин дав визначення терміну "замикання" в 1964 році з середовищем і контрольної частиною, застосовуваних на машині SECD з метою оцінки лямбда-виразів, пов'язаних лексичної середовищем, що призводило до закриття їх або замикання в javascript.


Таке пояснення увійшло в 1975 році як лексично обмежений варіант LISP і стало широко поширеним. Лексична середовище є множиною дійсних змінних в програмі. Вона складається з внутрішньої лексичної середовища і посилань на зовнішнє середовище, так звану нелокальными змінними. Лексичні замикання в javascript є функціями з її зовнішнім середовищем. Як і в javascript, всі змінні мають посилання на тип. JS використовує тільки прив'язку за посиланням - яка відповідає в C ++ 11 а час життя перехресних локальних змінних, захоплених функцією, поширюється на час життя функції.
[thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_422.jpeg[/thumb]

Першокласні функції

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


[thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_483.jpeg[/thumb]
У цьому прикладі вираз lambda (lambda (book) (>= (book-sales book) threshold)) з'являється всередині функції best-selling-books. Коли обчислюється лямбда-вираз, схема створює замикання, яке складається з коду для вираження лямбда і посилання на threshold змінну, яка є вільною змінною всередині лямбда виразу. Замикання потім передається filter функції, яка викликає її неодноразово, щоб визначити, які книги мають бути додані в список результатів і які повинні бути відкинуті. Оскільки тут замикання в значенні threshold, остання може використовувати її кожен раз, коли її filter викликає. Сама функція filter може бути визначена в абсолютно окремому файлі. Ось той же приклад, переписаний в JS. Він демонструє, як працюють замикання під капотом у javascript.
[thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_624.jpeg[/thumb]
Ключове слово тут використовується замість глобальної filter функції, але в іншому структура і ефект коду є однаковими. Функція може створити замикання і повернути її оскільки вона в цьому випадку переживає виконання функції з f змінними і dx продовжують функціонувати після derivative, навіть якщо виконання залишило їх в сферу дії, і вони більше не видно.
У мовах без замикань час життя автоматичної локальної змінної збігається з виконанням кадру стека, де оголошена ця змінна. У мовах з javascript замикання і функції iife, змінні повинні продовжувати існувати до тих пір, поки будь-які існуючі блокування мають посилання на них. Це найчастіше реалізується з використанням деякої форми збору сміття.

Області застосування

[thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_525.jpeg[/thumb]
Перевага замикання полягає в тому, що воно зберігає область дії, «ланцюг видимості» зовнішнього або «батьківського» контексту виконання. Така поведінка може бути використаний кількома способами і стало корисним засобом для запобігання цілого ряду помилок javascript. Одним з найбільш поширених є проблема «петлі». Проблема з циклом виникає, коли користувач створює функцію в циклі і очікує , що поточне значення змінної залишиться в цій новій функції, навіть якщо воно змінюється в контексті циклів перед викликом нової функції. Замикання, використовуються таким чином, більше не мають посилальної прозорості і, отже, більше не є чистими функціями, тим не менш, вони зазвичай використовуються в нечистих функціональних мовах, таких як Scheme. Для того щоб зрозуміти, що таке замикання в javascript, потрібно розглянути випадки їх використання. Насправді на практиці вони мають багато застосувань:
  • Їх можна використовувати для визначення структур управління. Наприклад, всі стандартні структури управління Smalltalk, включаючи гілки (if /then /else) і цикли (while і for), визначаються з використанням об'єктів, методи яких беруть замикання. Користувачі також можуть легко використовувати замикання для визначення структури управління. У мовах, що реалізують призначення, можна створювати її багатофункціональну середу, дозволяючи спілкуватися конфіденційно і змінювати це середовище. Замикання використовується для реалізації об'єктних систем.
  • Створення як приватних, так і загальнодоступних методів змінних, використовуючи шаблони модуля. З-за того, що повертаються функції успадковують область батьківського функції, вони доступні всім змінним і аргументів в даному контексті.
  • Воно корисно в ситуації, коли функція використовує один і той же ресурс для кожного виклику, але і створює сам ресурс для нього. Ця обставина робить метод неефективним, яке усувається виключно замиканням.
  • Функціонування в javascript

    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_546.jpeg[/thumb]
    Згідно MDN (Mozilla Developer Network) «Closures - це функції з незалежними змінними, які «запам'ятовують» середовище свого створення». І, як правило, коли функція завершується, її локальні змінні більше не існують. Зрозуміти, як працюють замикання в javascript, можна розглянувши кілька механізмів. Перший - формальна логіка. Наприклад, застосувавши функцію logName, яка приймає одне ім'я в якості параметра і реєструє його. Потім створюю цикл for, щоб перебирати список імен, задавати 1-й тайм-аут, а потім викликати функцію logName, що проходить в поточному імені.
    У першокласному мовою функції можна маніпулювати так само, як і інші типи даних, наприклад, int або string. Тільки цей механізм дозволяє багатьом створювати неймовірні речі, наприклад, призначати функцію змінної для її подальшого виклику або передавати її як параметр функції.
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_177.jpeg[/thumb]
    Цей принцип використовується багатьма структурами, а також обробниками подій DOM. Спочатку «слухають» подія, потім призначають функцію зворотного виклику, яка буде викликатися кожен раз при спрацьовуванні події.
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_428.jpeg[/thumb]

    Анонімні функції

    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_469.jpeg[/thumb]
    Анонімна функція - це функція без імені. Практично програмісти-початківці зустрічають їх щодня, не розуміючи гру з цифрами. Наприклад, виконуючи операцію додавання, можна перейти через змінні, наприклад:

  • var x = 3;
  • y = 5;
  • var z = x + y.
  • Або якщо не маєте наміру повторно обробити номери:var z = 3 + 5; Це і є анонімні номери. Для анонімних функцій можна оголосити їх, коли їх використовують «на льоту» - без проходження змінної. Наприклад, взяти функцію do з раніше: do( function() {alert("Ceci est une fonction anonyme."); } ); Більше того, існує альтернативний синтаксис оголошення функції, який підкреслює, що одночасно функції можуть бути анонімними і посилатися на прості змінні, що є зручним способом установки функції зворотного виклику.

    Визначення функцій

    Насправді це той же механізм, але з цієї точки зору він дозволить побачити, як відбувається замикання функції зсередини. Як видно, оскільки функції є змінними, як і інші, немає причин, за якими можна визначити їх локально. У мові нульового порядку, такому як C, C ++ і Java, всі функції визначаються на одному рівні видимості, у тому самому класі або на глобальному рівні. З іншого боку, в javascript локальна функція зникає, як і інші локальні змінні, як тільки закінчується батьківська функція, тому він не видний з інших функцій.
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_6810.jpeg[/thumb]
    Це насправді складно, але в javascript є спосіб відслідковувати видимість змінних, і навіть двома способами. Призначення глобальної змінної в javascript мають такий же механізм, як і в Java - складні об'єкти, масиви, елементи DOM і інші передаються по посиланню, тому в наступному коді: var tab =[51, 42, 69]; var tab2 = tab. Де, tab і tab2 - два посилання на одну і ту ж таблицю, технічно це покажчики, керовані збирачем сміття. Функції також передаються по посиланню. Змінна globalFn більше не прихована. Порядок дозволяє це робити, що продемонстровано на прикладі задачі на замикання javascript.
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_9511.jpeg[/thumb]
    Ось як можна отримати функцію з локального контексту, якщо функція задовольняє іншим локальним змінним. Простий приклад: auto-increment, функція, яка повертає ціле число, яке збільшується на 1 при кожному виклику. Конкретно, потрібна функція inc, яка веде себе таким чином: inc(); //retourne 0 inc(); //retourne 1 inc(); //retourne 2 inc(); //retourne 3 //etc. З замиканням це виглядає: function makeInc() {var x = 0; return function() {return x++;}} var inc = makeInc(); В останньому рядку в той момент, коли створюється змінна функція inc, вона несе в собі якісь змінні, які є навколо, в цьому випадку x. Він створює якийсь невидимий об'єкт навколо функції, який містить цю змінну. Цей об'єкт є функцією замикання javascript. При цьому кожна копія функції буде мати своє замикання: var inc1 = makeInc(); var inc2 = makeInc(); inc1(); //0 inc1(); //1 inc1(); //2 inc2(); //0 inc1(); //3 inc2(); //1 inc2(); //2 Як видно, замикання дуже корисно в багатьох випадках.

    Конфлікти імен змінних

    Щоб уникнути конфліктів імен змінних, зазвичай використовуються простору імен. В javascript простору імен представляють собою об'єкти, подібні яким іншим.
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_5412.jpeg[/thumb]
    Природно, A. x і B. x це не одна і та ж змінна. Проте якщо просто потрібно запустити скрипт, не вимагаючи збереження змінних для інших, можна використовувати анонімну функцію, як замикання. Це дає дещо дивний синтаксис. Хоча два рядки коду в середині досить звичайні, з іншого боку, функція, яка знаходиться навколо, виконується «на льоту». Звертають увагу на круглі дужки ()в кінці. І щоб мати можливість робити замикання, анонімна функція сама повинна бути оточена круглими дужками. У цієї анонімної функції використовують локальну змінну, абзац. Це відмінний спосіб запобігти конфлікти імен або незграбність, але також і проти XSS атак користувальницькі змінні захищені, ніхто не може їх змінити, щоб торкнутися поведінка скрипта. Існує варіант: (function() {//}()); При цьому звертають увагу на перестановку дужок. Різницю між цими двома варіантами досить складно пояснити, оскільки вони пов'язані з тим, як код читається лексичним аналізатором. В обох випадках функція вважається вираженням, але цей вираз не оцінюється одночасно. Просто потрібно пам'ятати, що він приймає дві пари круглих дужок: одну навколо функції і одну за нею.

    javascript-програмування в циклах

    Коли користувач виконує великі обсяги javascript-програмування, йому важко уникнути циклів. Когось це зводить з розуму, після чого вони приходять до думки, що всяка реалізація javascript має серйозну помилку. Якщо у розробника вже є цикл, який він не хоче перетворювати, щоб використовувати функцію ітератора, все, що йому потрібно зробити, - це замикання, в якому він визначає нові змінні. Вони фіксують поточне значення змінних, і змінюються на кожній ітерації. Прийомом для захоплення змінних є те, що зовнішнє замикання виконується відразу ж під час поточної ітерації циклу. Можна використовувати один з цих двох зразкових підходів
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_7913.jpeg[/thumb]
    Тепер є ще одне спрощене вирішення цієї проблеми, оскільки let ключове слово підтримується як в Firefox, так і в Chrome. Воно є ключовим слово замість var змінного блоку. Let працює магічним чином, тому що оголошується нову змінну j, значення i якої фіксується замиканням всередині циклу. Однак треба враховувати, що вона не продовжує існувати після кінця однієї ітерації циклу, оскільки воно локально.

    Петля і функція

    For Цикл в javascript не видається, так само як цикл for C або Java. Насправді це більше схоже на PHP. Найголовніше знання про цикли в JS полягає в тому, що вони не створюють область дії. JS не має блок сфери, тільки функцію обсягу. Це властивість можна розглянути на наступному фрагменті: function foo() {var bar = 1; for(var i = 0; i < 42; i++) {var baz = i;} /* more code */} Зрозуміло, що bar доступно у всій функції. До першої ітерації циклу baz буде мати значення undefined. Після циклу він буде мати значення 41 (і i буде 42). Таким чином, будь-яка змінна, оголошена в будь-якому місці функції, буде доступна скрізь, функції і буде мати значення тільки після того, як вона була призначена йому.

    Затвори і агрегування

    Замикання - це не що інше, як функції, всередині інших функцій, і передаються в якийсь інший контекст. Вони називаються замиканням, так як вони закривають через локальні змінні, тобто доступні до інших функцій сфери. Наприклад, час, x визначене як параметр foo, і var bar = foo(2)() повернеться 84. Повертається функція foo має доступ x. Це все важливо, тому що допомагає розробникам створювати функції усередині циклів, залежать від змінних циклу. Розглянемо цей фрагмент, який присвоює click-обробник різним елементам: //elements is an array of 3 DOM elements var values =['foo', 'bar', 'baz']; for(var i = 0 l = elements.length; i< l; i++) {var data = values[i]; elements[i].onclick = function() {alert(data); }; } Значення, яке вони будуть використовувати alert при натисканні, буде однаково для всіх, а саме baz. До того часу викликається обробник подій, for вже завершено. JS не має області блоку, тобто всі обробники використовують посилання на одну і ту ж data змінну. Після петлі, це значення буде values. Кожне оголошення змінної створює одне місце в пам'яті для зберігання даних. В for ці дані знову і знову змінюються, положення в пам'яті залишається незмінним. Кожен оброблювач подій має доступ до однієї і тієї ж позиції в пам'яті. Єдине рішення - ввести ще одну область, яка «фіксує» поточне значення data. JS має тільки область функцій. Тому вводиться інша функція. Приклад: function createEventHandler(x) {return function() {alert(x); } } for(var i = 0 l = elements.length; i< l; i++) {var data = values[i]; elements[i].onclick = createEventHandler(data); } Це працює, тому що значення data буде зберігатися в локальній області, createEventHandler і ця функція виконується на кожній ітерації. Це можна записати коротше, використовуючи відразу виконувані функції: for(var i = 0 l = elements.length; i< l; i++) {var data = values[i]; elements[i].onclick = (function(x) {function() {alert(x); }; } (data)); }

    Практичний приклад замикання в javascript

    Якщо користувач виконує замикання прямо над кодом в браузері, він може зіткнутися з проблемою, так як може зробити будь-яку синтаксичну помилку. Якщо він виконує код безпосередньо в браузері, то шанси дуже високі, щоб не скомпілювати процес компіляції webpack. Можливі рішення: //main.js function work(name){ return function (topic) { console.log( What is ${topic} in ${name} ); } } work('javascript')('Closure'); Спочатку викликається робота функції і передається аргумент імені. Тепер ця функція лексики також повертає функцію, яка також приймає аргумент теми. Ця функція реєструє висновок, а на виході є доступ до змінної. Область функцій Insider не обмежується цією функцією, тому концепція називається Closure, оскільки вона має доступ до цієї області зовнішнього параметра. Повертається функція має доступ до зовнішньої лексичної області або контекстів. Коли розробник викликає функцію, яка також повертає її, то спочатку звані змінні функції завжди доступні для внутрішньої функції. Далі приклад з наступним кодом.
    [thumb]http://hi-news.pp.ua/uploads/posts/2018-11/zamikannya-v-javascript-praktichniy-priklad-osoblivost-ta-pravila_5314.jpeg[/thumb]

    Приклад внутрішньої функції

    Детальніше про замиканні в javascript можна розповісти на другому прикладі. Тепер ця середовище виконання знищується, але ім'я параметра все ще існує. Створюється нова внутрішня функціональна середовище, що є анонімної функцією. Вона має доступ до області зовнішньої лексичної середовища. Таким чином, у змінній оточення все ще існує так, що анонімна функція має доступ до змінної імені друкує в консолі, наприклад, «Що таке замикання в javascript ». Внутрішня анонімна функція //main.js function factory(){var products =[]; for(var i=0; i <2; i++){products.push(function () {console.log(i); }); } return products; } var soap = factory(); soap[0](); soap[1](); Результат цього прикладу досить незначний і дорівнює 2. Коли мило - soap[0]() називається зовнішньою змінною контексту, завжди 2 тому що в циклі умова хибна в i<2 тому при цьому значення i дорівнює 2 а під час виклику потрібно надрукувати значення в консоль так, вона завжди пише 2. Те ж саме для мила - soap[1]().

    Створення функцій «на льоту»

    Можна створити фабрику функцій - functionFactory, яка виконує власні завдання. Результуюча функція від фабрики функцій буде замиканням, що запам'ятовує середу створення. var functionFactory = function(num1) {return function(num2) {return num1 * num2; } } Вищенаведене дозволяє передати один номер functionFactory. Потім functionFactory повертає Замикання, запам'ятовуючий значення num1. Отримана функція множить оригінальні num1 разів величина num2 який передається при виклику. var mult5 = functionFactory(5); var mult10 = functionFactory(10); Вищенаведене просто створює функції mult5 і mult10. Тепер можна посилатися на будь-яку з цих функцій, передаючи новий номер, який потрібно помножити на 5 або 10. Тепер можна побачити результат. > mult5(3) 15 > mult5(5) 25 > mult10(3) 30 > mult10(5) 50 Замикання - одна з найбільш потужних функцій javascript, але вона не може бути використана правильно без розуміння суті. Їх відносно легко створити випадково, ось чим небезпечні замикання javascript. Їх створення має потенційно шкідливі наслідки, особливо в деяких щодо загальних середовищах веб-браузера. Щоб уникнути випадкового зіткнення з недоліками і скористатися перевагами, які вони пропонують, необхідно зрозуміти їх механізм.