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

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

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

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

Закриття були розроблені в 1960-х роках для механічної оцінки виразів в обчисленні і застосовані в 1970 році як особливість мови програмування PAL для підтримки функцій першого класу з лексичною сферою. Пітер Ландін дав визначення терміну "" замикання "" в 1964 році з середовищем і контрольною частиною, що застосовуються на машині SECD з метою оцінки лямбда-виразів, пов 'язаних лексичним середовищем, що призводило до закриття їх або замикання в Javascript.


Таке пояснення увійшло в 1975 році як лексично обмежений варіант LISP і стало широко поширеним. Лексичне середовище є безліччю дійсних змінних у програмі. Вона складається з внутрішнього лексичного середовища і посилань на зовнішнє середовище, зване нелокальними змінними.

Лексичні замикання в Javascript є функціями з її зовнішнім середовищем. Як і JavaScript, всі змінні мають посилання на тип. JS використовує тільки прив 'язку за посиланням - яке відповідає в C++ 11, а час життя нелокальних змінних, захоплених функцією, поширюється на час життя функції.

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

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

У цьому прикладі вираз lambda (lambda (book) (> = (book-sales book) threshold)) з 'являється всередині функції best-selling-books. Коли вираховується лямбда-вираз, схема створює замикання, що складається з коду для вираження лямбда і посилання на threshold змінну, яка є вільною змінною всередині виразу лямбда. Замикання потім передається filter функції, яка викликає її неодноразово, щоб визначити, які книги повинні бути додані до списку результатів і які повинні бути відкинуті.

Оскільки тут замикання у значенні threshold, остання може використовувати її кожен раз, коли її filter викликає. Сама функція filter може бути визначена в абсолютно окремому файлі. Ось той же приклад, переписаний в JS. Він демонструє, як працюють замикання під капотом в Javascript.

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


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

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

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

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

  1. Їх можна використовувати для визначення структур управління. Наприклад, всі стандартні структури управління Smalltalk, включаючи гілки (if/then/else) і цикли (while і for), визначаються з використанням об 'єктів, методи яких приймають замикання. Користувачі також можуть легко використовувати замикання для визначення структури управління. У мовах, що реалізують призначення, можна створювати її багатофункціональне середовище, дозволяючи спілкуватися конфіденційно і змінювати це середовище. Замикання використовується для реалізації об 'єктних систем.
  2. Створення як приватних, так і загальнодоступних методів змінних використовуючи шаблони додатка. Через те, що повертаються функції успадковують область батьківської функції, вони доступні всім змінним і аргументам в даному контексті.
  3. Воно корисне в ситуації, коли функція використовує один і той же ресурс для кожного виклику, але і створює сам ресурс для нього. Ця обставина робить метод неефективним, який усувається виключно замиканням.

Функціонування JavaScript

Згідно з MDN (Mozilla Developer Network) "Closures - це функції з незалежними змінними, які" запам 'ятовують "середовище свого створення". І, як правило, коли функція завершується, її локальні змінні більше не існують. Зрозуміти, як працюють замикання в Javascript, можна розглянувши кілька механізмів. Перший - формальна логіка. Наприклад, застосувавши функцію logName, яка приймає одне ім 'я як параметр і реєструє його. Потім створюю цикл for, щоб перебирати список імен, задавати 1-й тайм-аут, а потім викликати функцію logName, що проходить в поточному імені.

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

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

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

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


  • 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 локальна функція зникає, як і інші локальні змінні, як тільки закінчується батьківська функція, тому вона не видна з інших функцій.

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

var tab = [51, 42, 69]; var tab2 = tab.


Де, tab і tab2 - два посилання на одну і ту ж таблицю, технічно це покажчики, керовані складальником сміття. Функції також передаються за посиланням. Змінна globalFn більше не прихована. Порядок дозволяє це робити, що продемонстровано на прикладі завдання на замикання Javascript.

Ось як можна отримати функцію з локального контексту, якщо функція задовольняє іншим локальним змінним. Простий приклад: 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 простори імен - це об 'єкти, подібні до інших.

Природно, A.x і B.x це не одна і та ж змінна. Однак якщо просто потрібно запустити скрипт, не вимагаючи збереження змінних для інших, можна використовувати анонімну функцію, як замикання. Це дає дещо дивний синтаксис. Хоча два рядки коду в середині досить звичайні, з іншого боку, функція, яка знаходиться навколо, виконується "на льоту". Звертають увагу на круглі дужки () наприкінці. І щоб мати можливість робити замикання, анонімна функція сама повинна бути оточена круглими дужками.

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

Існує варіант: (function() {// ...}());

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

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

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

Тепер є ще одне спрощене рішення цієї проблеми, оскільки 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 зберігатиметься в локальній області, creceEventHandler і ця функція виконується на кожній ітерації. Це можна записати коротше, використовуючи відразу виконувані функції:

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, оскільки вона має доступ до цієї області зовнішнього параметра. Функція повернення має доступ до зовнішньої лексичної області або контекстів. Коли розробник викликає функцію, яка також повертає її, спочатку звані змінні функції завжди доступні для внутрішньої функції. Далі приклад з наступним кодом.

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

Детальніше про замикання в 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] ().

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

Можна створити фабрику функцій - func^ Factory, яка виконує власні завдання. Результуюча функція від фабрики функцій буде замиканням, що запам 'ятовує середовище створення.

var functionFactory = function(num1) {return function(num2) {return num1 * num2;

}

}

Вищенаведене дозволяє передати один номер func^ Factory. Потім func^ Factory повертає Замикання, яке запам 'ятовує значення 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. Їх створення має потенційно шкідливі наслідки, особливо в деяких щодо загальних середовищах веб-браузера. Щоб уникнути випадкового зіткнення з вадами і скористатися перевагами, які вони пропонують, необхідно зрозуміти їх механізм.