Що таке "закриття"?


432

Я поставив запитання про згадування про закриття та закриття. Що таке закриття? Як це стосується каррі?


22
Тепер, що саме таке закриття ??? Деякі відповіді кажуть, що закриття - це функція. Деякі кажуть, що це стек. Деякі відповіді кажуть, що це "приховане" значення. Наскільки я розумію, це функція + додані змінні.
Roland

3
Пояснює , що таке закриття: stackoverflow.com/questions/4103750 / ...
dietbuddha

Також подивіться, що таке закриття? на softwareengineering.stackexchange
B12Toaster

Пояснює, що таке закриття та звичайний випадок використання: trungk18.com/experience/javascript-closure
Sasuke91

Відповіді:


743

Змінна область застосування

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

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

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

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

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

Ось як ми зазвичай очікуємо, що справи працюватимуть.

Закриття є стійкою локальною змінною сферою

Закриття - це постійна область, яка тримається на локальних змінних, навіть після того, як виконання коду перемістилося з цього блоку. Мови, які підтримують закриття (наприклад, JavaScript, Swift та Ruby), дозволять вам зберігати посилання на область (включаючи його батьківські області) навіть після того, як блок, в якому зазначені змінні були оголошені, закінчив виконувати, за умови збереження посилання до цього блоку чи десь функціонувати.

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

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

Наприклад

Ось дуже простий приклад JavaScript, який ілюструє суть:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

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

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

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

Як ви могли б здогадатися, коли я називаю fnc()це, виводиться значенняa , яке є "1".

Мовою без закриття змінна aбула б зібрана та викинута сміття, коли функція outerвиходила. Виклик fnc призведе до помилки, оскільки її aбільше не існує.

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

aналежить до сфери дії outer. Область innerмає батьківський вказівник на область outer. fnc- це змінна, яка вказує на inner. aзберігається до тих пір, поки fncзберігається. aзнаходиться в межах закриття.


116
Я подумав, що це досить гарний і легкий для розуміння приклад.
користувач12345613

16
Дякую за дивовижне пояснення, я бачив багатьох, але саме зараз я отримав це.
Димитър Димитров

2
Чи можу я мати приклад того, як це працює в такій бібліотеці, як JQuery, як зазначено у другому до останнього абзацу? Я цього не зовсім розумів.
DPM

6
Привіт Джубат, так, відкрий jquery.js і подивися на перший рядок. Ви побачите, що функція відкрита. Тепер перейдіть до кінця, ви побачите window.jQuery = window. $ = JQuery. Потім функція закривається і виконується самостійно. Тепер у вас є доступ до функції $, яка, в свою чергу, має доступ до інших функцій, визначених у закритті. Це відповідає на ваше запитання?
суперлюмінація

4
Найкраще пояснення в Інтернеті. Шлях простіший, ніж я думав
Mantis

95

Наведу приклад (у JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

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

Ось знову мій приклад:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

Що ви можете бачити, це те, що при виклику add з параметром a (який дорівнює 3) це значення міститься у закритті повернутої функції, яку ми визначаємо як add3. Таким чином, коли ми викликаємо add3, він знає, де знайти значення для виконання додавання.


4
IDK, якою мовою (можливо F #) ви використовували вищевказану мову. Чи можете, будь ласка, навести вище приклад у псевдокоді? Мені важко це зрозуміти.
користувач


3
@KyleCronin Чудовий приклад, дякую. Питання: Чи правильніше сказати "приховане значення називається закриттям", або "функція, яка приховує значення, є закриттям"? Або "процес приховування значення - це закриття"? Дякую!

2
@RobertHume Добре запитання. Семантично поняття «закриття» є дещо неоднозначним. Моє особисте визначення полягає в тому, що поєднання як прихованого значення, так і використання функції, що додає, являє собою закриття.
Кайл Кронін

1
@KyleCronin Спасибі - у мене є схема в середньостроковій перспективі в понеділок. :) Мені хотілося, щоб в моїй голові була концепція "закриття". Дякуємо, що опублікували цю чудову відповідь на питання ОП!

58

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


14
Це не лише стек - це лексичні рамки, що додаються, які зберігаються, незалежно від того, зберігаються вони у стеці чи купі (або обох).
Метт Фенвік

38

Перш за все, всупереч тому, що вам каже більшість людей тут, закриття - це не функція ! Так що це таке?
Це набір символів, визначених у "оточуючому контексті" функції (відомий як її середовище ), який робить його ЗАКРИТИм виразом (тобто виразом, у якому кожен символ визначений і має значення, тому його можна оцінити).

Наприклад, якщо у вас є функція JavaScript:

function closed(x) {
  return x + 3;
}

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

Але якщо у вас є така функція:

function open(x) {
  return x*y + 3;
}

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

Це yвимагає визначення, але це визначення не є частиною функції - воно визначене деінде, у його "оточуючому контексті" (також відомому як середовище ). Принаймні, на це ми сподіваємось: P

Наприклад, це можна визначити глобально:

var y = 7;

function open(x) {
  return x*y + 3;
}

Або це може бути визначено у функції, яка завершує його:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

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

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

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

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

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

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

Більше про теорію, що стоїть тут: https://stackoverflow.com/a/36878651/434562

Варто зазначити, що у наведеному вище прикладі функція обгортки повертає свою внутрішню функцію як значення. Момент, коли ми називаємо цю функцію, може бути віддалений у часі з моменту визначення (або створення) функції. Зокрема, його функція обгортання більше не працює, і її параметрів, що були у стеку викликів, вже немає: P Це створює проблему, оскільки внутрішня функція повинна yбути там, коли вона викликається! Іншими словами, він вимагає, щоб змінні від його закриття якимось чином пережили функцію обгортки і були там, коли потрібно. Отже, внутрішня функція повинна зробити знімок цих змінних, які роблять його закриттям і зберігають їх десь безпечними для подальшого використання. (Десь поза стеком викликів.)

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


1
Аналогія, яка може допомогти новачкам у цьому, - це закриття, пов'язане з усіма вільними кінцями , саме це робить людина, коли прагне закрити (або вона вирішує всі необхідні посилання, або ...). Що ж, це допомогло мені подумати про це так: o)
Буде Кроуфорд

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

29

Закриття - це функція, яка може посилатись на стан в іншій функції. Наприклад, у Python для цього використовується закриття "внутрішній":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

23

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

Для початку я повинен ввести поняття простору імен. Коли ви вводите команду в інтерпретатор схеми, вона повинна оцінити різні символи у виразі та отримати їх значення. Приклад:

(define x 3)

(define y 4)

(+ x y) returns 7

Визначення виразів зберігають значення 3 у місці для x та значення 4 у місці для y. Потім, коли ми викликаємо (+ xy), інтерпретатор шукає значення в просторі імен і може виконати операцію та повернути 7.

Однак у схемі є вирази, які дозволяють тимчасово перекрити значення символу. Ось приклад:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

Що дозволено ключовому слову, це вводить нову область імен з x як значення 5. Ви помітите, що він все ще може бачити, що y дорівнює 4, а сума повертається до 9. Ви також можете бачити, що після закінчення виразу x повертається до рівня 3. У цьому сенсі х було тимчасово замасковано місцевим значенням.

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

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

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

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

Ми визначаємо x як 3, а плюс-x - його параметр, y плюс значення x. Нарешті, ми називаємо плюс-x у середовищі, де х маскується новим x, цей оцінюється 5. Якщо ми просто зберігаємо операцію (+ xy) для функції plus-x, оскільки ми знаходимось у контексті з x, якщо 5 - результат, що повертається, буде 9. Це те, що називається динамічним оцінюванням.

Однак у Scheme, Common Lisp і багатьох інших мовах є те, що називається лексичним обстеженням - окрім зберігання операції (+ xy), ми також зберігаємо простір імен у цій конкретній точці. Таким чином, коли ми шукаємо значення, ми можемо побачити, що x у цьому контексті дійсно 3. Це закриття.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

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


гаразд, завдяки вашій відповіді, я думаю, що нарешті маю уявлення про те, про що йдеться. Але є одне велике питання: "ми можемо використовувати пов'язаний список для зберігання стану простору імен під час визначення функції, що дозволяє нам отримувати доступ до змінних, які в іншому випадку більше не будуть мати область". Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Лазер

@Laser: Вибачте, це речення мало особливого сенсу, тому я його оновив. Сподіваюся, зараз це має більше сенсу. Крім того, не думайте про пов'язаний список як про деталі реалізації (оскільки це дуже неефективно), а як простий спосіб осмислити, як це можна зробити.
Кайл Кронін

10

Ось справжній приклад у світі, чому замикаються дупи ... Це прямо не в моєму коді Javascript. Дозвольте проілюструвати.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

А ось як би ви його використовували:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Тепер уявіть, що ви хочете, щоб відтворення почалося відкладено, як, наприклад, через 5 секунд після запуску цього фрагмента коду. Ну, це легко delayі закривається:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Коли ви зателефонували за delayдопомогою 5000ms, перший фрагмент запускається і зберігає передані аргументи під час його закриття. Потім через 5 секунд, коли setTimeoutвідбувається зворотний виклик, закриття все ще підтримує ці змінні, тож воно може викликати вихідну функцію з вихідними параметрами.
Це тип каррінгу, або функція прикраси.

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


1
Слід зазначити, що розширення мови або хост-об’єктів, як правило, вважається поганою справою, оскільки вони є частиною глобального простору імен
Джон Кук,

9

Функції, що не містять вільних змінних, називаються чистими функціями.

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

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure


Чому це мінус? Це насправді набагато більше "на правильному шляху" з цим розрізненням на вільні змінні та зв'язані змінні, чисті / закриті функції та нечисті / відкриті функції, ніж більшість інших нечітких відповідей тут: P (знижка за плутанину закриття з функціями будучи закритим).
SasQ

Я не маю ідеї, насправді. Ось чому StackOverflow смокче. Подивіться лише на джерело моєї відповіді. Хто може з цим посперечатися?
саундйогі

Так не смокче, і я ніколи не чув про термін "вільна змінна"
Кай

Важко говорити про закриття, не згадуючи вільних змінних. Просто подивіться на них. Стандартна термінологія CS.
ComDubh

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

7

тл; д-р

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

Поглиблення пояснення стилю Вікіпедії

За даними Вікіпедії, закриття :

Прийоми реалізації лексично розмахування імен в мовах з функціями першого класу.

Що це означає? Давайте розглянемо деякі визначення.

Я поясню закриття та інші пов'язані визначення, використовуючи цей приклад:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

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

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

У наведеному вище прикладі startAtповертає ( анонімну ) функцію, функції якої призначаються closure1та closure2. Отже, як ви бачите, JavaScript розглядає функції так само, як і будь-які інші об'єкти (громадяни першого класу).

Пов’язування імені

Прив’язка до імен полягає в тому, щоб з’ясувати, на які дані посилається змінна (ідентифікатор) . Тут дійсно важливим є обсяг, оскільки саме ця річ визначає, як вирішується прив'язка.

У наведеному вище прикладі:

  • У внутрішній області анонімної функції, yце пов'язано з3 .
  • В startAtрамках «S, xпов'язаний з 1або 5( в залежності від закриття).

Всередині області анонімної функції xне пов'язано жодне значення, тому її потрібно вирішити у верхній ( startAts) області.

Лексичне обстеження

Як говорить Вікіпедія , сфера застосування:

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

Існує дві методики:

  • Лексичне (статичне) масштабування: визначення змінної вирішується шляхом пошуку її блоку, що містить, або функції, тоді, якщо це не вдається шукати зовнішній блок, що містить, і так далі.
  • Динамічне масштабування: шукається функція виклику, потім функція, яка викликала цю функцію виклику, і так далі, просуваючи стек викликів.

Щоб отримати додаткові пояснення, ознайомтеся з цим питанням і подивіться у Вікіпедію .

У наведеному вище прикладі ми можемо побачити, що JavaScript має лексичний діапазон, тому що, коли xце вирішено, пошук прив'язки шукається у верхній області ( startAts) на основі вихідного коду (анонімна функція, яка шукає x, визначена всередині startAt) та не ґрунтуючись на стеці викликів, спосіб (область, де) викликалася функція.

Згортання (закривання) вгору

У нашому прикладі, коли ми зателефонуємо startAt, він поверне функцію (першого класу), якій буде призначено closure1і, closure2таким чином, буде створено закриття, оскільки передані змінні 1та 5будуть збережені в межах startAtсфери, яка буде додана до повернутого анонімна функція. Коли ми називаємо цю анонімну функцію через closure1і closure2з тим самим аргументом ( 3), значення yбуде знайдено негайно (як це параметр цієї функції), але xне обмежене в області анонімної функції, тому дозвіл продовжується в (лексично) верхній діапазон функцій (який був збережений у закритті) деx виявлено, що вона пов'язана з будь-яким 1або5. Тепер ми знаємо все для підсумовування, щоб результат можна було повернути, а потім надрукувати.

Тепер ви повинні зрозуміти закриття та те, як вони поводяться, що є основоположною частиною JavaScript.

Заготівля

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


5

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

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

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

Приклад закриття :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

Вихідний результат становитиме 20, що становить суму власної змінної внутрішньої функції, змінну зовнішньої функції та загальну величину змінної.


4

У звичайній ситуації змінні пов'язані правилом визначення: Місцеві змінні працюють лише в межах визначеної функції. Закриття - це спосіб тимчасового порушення цього правила для зручності.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

у наведеному вище коді lambda(|n| a_thing * n}- це закриття, оскільки a_thingпосилається лямбда (творець анонімної функції).

Тепер, якщо ви помістите отриману анонімну функцію у змінну функції.

foo = n_times(4)

foo порушить нормальне правило масштабування і почне використовувати 4 внутрішньо.

foo.call(3)

повертає 12.


2

Коротше кажучи, покажчик функції - це лише вказівник на місце в базовій коді програми (наприклад, лічильник програм). Тоді як закриття = покажчик функції + кадр стека .

.


1

• Закриття - це підпрограма та середовище посилання, де вона була визначена

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

- Мова зі статичним діапазоном, який не дозволяє вкладені підпрограми, не потребує закриття

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

- Для підтримки закриттів, можливо, потрібно буде впроваджувати необмежену кількість деяких змінних (оскільки підпрограма може отримати доступ до нелокальної змінної, яка зазвичай більше не існує)

Приклад

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

Ось ще один приклад із реального життя та використання мови сценаріїв, популярної в іграх - Lua. Мені потрібно було трохи змінити спосіб роботи функції бібліотеки, щоб уникнути проблеми з недоступністю stdin.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

Значення old_dofile зникає, коли цей блок коду закінчує область його дії (тому що він локальний), проте значення було вкладено в закриття, тому нова перероблена функція дофіля МОЖЕ отримати доступ до нього, а точніше копія, що зберігається разом із функцією як 'цінність'.


0

З Lua.org :

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


0

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

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

Функція g- це закриття: gзакривається a. Так gможна порівняти з функцією-членом, aможна порівняти з полем класу, а функцію - fз класом.


0

Закриття Кожен раз, коли у нас є функція, визначена всередині іншої функції, внутрішня функція має доступ до змінних, оголошених у зовнішній функції. Закриття найкраще пояснити на прикладах. У Лістингу 2-18 ви бачите, що внутрішня функція має доступ до змінної (змінноїInOuterFunction) із зовнішньої області. Змінні у зовнішній функції були закриті (або пов'язані) внутрішньою функцією. Звідси термін закриття. Сама по собі концепція досить проста і досить інтуїтивна.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

джерело: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf


0

Будь ласка, подивіться нижче код, щоб зрозуміти закриття більш глибоко:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Ось що буде виходити? 0,1,2,3,4не так буде 5,5,5,5,5через закриття

То як воно вирішиться? Відповідь нижче:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Дозвольте мені просто пояснити, коли створена функція нічого не відбувається, поки вона не викликала так для циклу в 1-му коді, який називається 5 разів, але не викликається негайно, коли він викликається, тобто через 1 секунду, а також це асинхронно, перш ніж це для циклу закінчено і збереже значення 5 в var i і, нарешті, виконайте setTimeoutфункцію п'ять разів та друкуйте5,5,5,5,5

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

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Докладніше, будь ласка, зрозумійте контекст виконання, щоб зрозуміти закриття.

  • Є ще одне рішення для вирішення цього за допомогою функції let (функція ES6), але під капотом працює вищевказана функція

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Більше пояснення:

У пам’яті, коли для циклу виконання малюнок зробити так:

Петля 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Петля 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Петля 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Петля 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Петля 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Тут я не виконується, і тоді після завершення циклу, var я зберігає значення 5 в пам'яті, але його область завжди видна в його дитячій функції, тому при виконанні функції всередині setTimeoutп’ять разів вона друкується5,5,5,5,5

щоб вирішити це використання IIFE, як пояснено вище.


Дякую за вашу відповідь. було б читабельніше, якби ви відділили код від пояснення. (не
майте

0

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

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

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

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

0

Закриття дуже легко. Ми можемо розглядати це так: закриття = функція + її лексичне середовище

Розглянемо наступну функцію:

function init() {
    var name = “Mozilla”;
}

Яким буде закриття у вищенаведеному випадку? Функція init () та змінні в її лексичному середовищі, тобто ім'я. Закриття = init () + ім'я

Розглянемо ще одну функцію:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

Які тут будуть закриття? Внутрішня функція може отримати доступ до змінних зовнішньої функції. displayName () може отримати доступ до імені змінної, оголошеної в батьківській функції, init (). Однак ті самі локальні змінні в displayName () будуть використовуватися, якщо вони існують.

Закриття 1: функція init + (змінна назва + функція displayName ()) -> лексична область

Закриття 2: функція displayName + (змінна назва) -> лексична область


0

Закриття забезпечують JavaScript державою.

Держава в програмуванні просто означає запам'ятати речі.

Приклад

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

У наведеному вище випадку стан зберігається у змінній "a". Далі ми додаємо 1 до "а" кілька разів. Ми можемо це зробити лише тому, що ми здатні «запам’ятати» цінність. Державник, "а", зберігає це значення в пам'яті.

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

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

Приклад

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

Як ми можемо отримати доступ до "ваги" в межах методу "візуалізації"? Ну, дякую державі. Кожен екземпляр Хліба класу може надати власну вагу, читаючи його з "стану", місця в пам'яті, де ми могли б зберігати цю інформацію.

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

Приклад

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

Наведений вище приклад досяг мети "збереження стану" змінною. Це чудово! Однак це має той недолік, що змінна (власник "держави") тепер піддається впливу. Ми можемо зробити краще. Ми можемо використовувати закриття.

Приклад

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

Це фантастично.

Тепер наша функція "count" може рахувати. Це вдається лише тому, що він може "утримувати" стан. Стан у цьому випадку є змінною "n". Ця змінна зараз закрита. Закритий у часі та просторі. Вчасно, тому що ви ніколи не зможете відновити його, змінити його, призначити йому значення або взаємодіяти безпосередньо з ним. У просторі, оскільки він географічно вкладений у функцію "countGenerator".

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

Ми приховуємо стан, змінну "n", що робить його приватною змінною! Ми також створили API, який може керувати цією змінною заздалегідь визначеним способом. Зокрема, ми можемо назвати API на зразок "count ()", який додає 1 до "n" з "відстані". Ні в якому разі, форма чи форма ніколи не зможуть отримати доступ до "n", окрім API.

JavaScript справді вражає своєю простотою.

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


0

Простий приклад у Groovy для довідки:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.