Час від часу я бачу, як згадуються "закриття", і я намагався шукати це, але Wiki не дає пояснення, яке я розумію. Може хтось допоможе мені тут?
Час від часу я бачу, як згадуються "закриття", і я намагався шукати це, але Wiki не дає пояснення, яке я розумію. Може хтось допоможе мені тут?
Відповіді:
(Відмова: це основне пояснення; що стосується визначення, я трохи спрощую)
Найпростіший спосіб подумати про закриття - це функція, яка може зберігатися як змінна (іменована як "функція першого класу"), яка має особливу можливість доступу до інших змінних, локальних до області, в якій вона була створена.
Приклад (JavaScript):
var setKeyPress = function(callback) {
document.onkeypress = callback;
};
var initialize = function() {
var black = false;
document.onclick = function() {
black = !black;
document.body.style.backgroundColor = black ? "#000000" : "transparent";
}
var displayValOfBlack = function() {
alert(black);
}
setKeyPress(displayValOfBlack);
};
initialize();
Функції 1, призначені document.onclickта displayValOfBlackзакриваються. Ви можете бачити, що вони обоє посилаються на булеву змінну black, але ця змінна призначається поза функцією. Оскільки blackце місцеве до області , де була визначена функція , покажчик на цей змінний зберігається.
Якщо ви розмістите це на сторінці HTML:
Це демонструє, що обидва мають доступ до одного black і того ж , і їх можна використовувати для зберігання стану без будь-якого об’єкта обгортки.
Заклик до setKeyPress- продемонструвати, як функцію можна передавати так само, як і будь-яку змінну. Обсяг зберігається в замиканні по - , як і раніше той , де була визначена функція.
Закриття зазвичай використовуються як обробники подій, особливо в JavaScript та ActionScript. Добре використання закриття допоможе вам неявно прив’язати змінні до обробників подій без необхідності створювати обгортку об'єктів. Однак необережне використання призведе до витоку пам’яті (наприклад, коли невикористаний, але збережений обробник подій - єдине, що може утримуватись у великих об'єктах пам’яті, особливо об’єктах DOM, запобігаючи збору сміття).
1: Насправді всі функції JavaScript є закриттями.
blackце оголошено всередині функції, чи не буде це знищено, коли стек розкручується ...?
blackце оголошено всередині функції, не було б це знищено". Пам'ятайте також, що якщо ви оголошуєте об'єкт у функції, а потім присвоюєте йому змінну, яка живе десь в іншому місці, цей об'єкт зберігається, оскільки на нього є інші посилання.
Закриття - це лише інший спосіб погляду на предмет. Об'єкт - це дані, які мають одну чи більше функцій, пов'язаних із ним. Закриття - це функція, яка має одну або кілька змінних, прив'язаних до неї. Два в основному однакові, принаймні на рівні реалізації. Справжня різниця полягає в тому, звідки вони беруться.
У об'єктно-орієнтованому програмуванні ви оголошуєте об’єктний клас, визначаючи його змінні учасника та його методи (функції члена) вперед, а потім створюєте екземпляри цього класу. Кожен екземпляр постачається з копією даних члена, ініціалізованою конструктором. Потім у вас є змінна типу об'єкта і передаєте її навколо як фрагмент даних, оскільки в центрі уваги є її природа як дані.
З іншого боку, об'єкт не визначається наперед, як клас об’єкта, або інстанціюється через виклик конструктора у вашому коді. Натомість ви записуєте закриття як функцію всередині іншої функції. Закриття може посилатися на будь-яку з локальних змінних зовнішньої функції, і компілятор виявляє, що переміщує ці змінні з простору стека зовнішньої функції в декларування прихованого об'єкта закриття. Потім у вас є змінна типу закриття, і, хоча це в основному об'єкт під кришкою, ви передаєте його навколо як функції посилання, оскільки фокус робиться на його природі як функції.
Термін закриття походить від того, що фрагмент коду (блок, функція) може мати вільні змінні, які закриваються (тобто прив'язуються до значення) оточенням, в якому визначений блок коду.
Візьмемо для прикладу визначення функції Scala:
def addConstant(v: Int): Int = v + k
У тілі функції є два імені (змінних) vі kвказують два цілих значення. Назва vпов'язана, оскільки вона оголошена як аргумент функції addConstant(дивлячись на декларацію функції, ми знаємо, що vпри виклику функції буде присвоєно значення). Назва kє вільною функцією wrt, addConstantоскільки функція не містить поняття щодо того, до якого значення kпов'язано (і як).
Щоб оцінити дзвінок, наприклад:
val n = addConstant(10)
ми повинні призначити kзначення, яке може статися лише в тому випадку, якщо ім'я kвизначено в контексті, в якому addConstantвизначено. Наприклад:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
def addConstant(v: Int): Int = v + k
values.map(addConstant)
}
Тепер, коли ми визначилися addConstantв контексті, де kвизначено, addConstantстало закриттям, оскільки всі його вільні змінні тепер закриті (прив’язані до значення): addConstantможна викликати і передавати навколо, як би функцією. Зверніть увагу , що вільна змінна kприв'язана до значення , коли кришка визначена , тоді як змінна аргумент vпов'язаний , коли кришка викликається .
Таким чином, закриття - це в основному функція або блок коду, який може отримати доступ до нелокальних значень через свої вільні змінні після того, як вони будуть пов'язані контекстом.
У багатьох мовах, якщо ви використовуєте закриття лише один раз, ви можете зробити це анонімним , наприклад
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
Зауважте, що функція без вільних змінних є особливим випадком закриття (з порожнім набором вільних змінних). Аналогічно, анонімна функція - це особливий випадок анонімного закриття , тобто анонімна функція - це анонімне закриття без вільних змінних.
Просте пояснення в JavaScript:
var closure_example = function() {
var closure = 0;
// after first iteration the value will not be erased from the memory
// because it is bound with the returned alertValue function.
return {
alertValue : function() {
closure++;
alert(closure);
}
};
};
closure_example();
alert(closure)використовуватиме створене раніше значення closure. alertValueПростір імен повернутої функції буде підключено до простору імен, в якому розміщена closureзмінна. Коли ви видалите всю функцію, значення closureзмінної буде видалено, але до цього часу alertValueфункція завжди зможе прочитати / записати значення змінної closure.
Якщо ви запускаєте цей код, перша ітерація присвоює closureзмінній значення 0 і перепише функцію на:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
А оскільки для виконання функції alertValueпотрібна локальна змінна closure, вона прив'язується до значення раніше призначеної локальної змінної closure.
І тепер кожен раз, коли ви викликаєте closure_exampleфункцію, вона буде виписувати збільшену величину closureзмінної, оскільки alert(closure)пов'язана.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
"Закриття" - це, по суті, місцевий стан і якийсь код, об'єднані в пакет. Зазвичай локальний стан походить із оточуючої (лексичної) сфери дії, і код є (по суті) внутрішньою функцією, яка потім повертається назовні. Закриття - це комбінація захоплених змінних, яку бачить внутрішня функція, та код внутрішньої функції.
Це одна з тих речей, яку, на жаль, трохи важко пояснити через незнайомість.
Одна з аналогій, яку я успішно використовував у минулому, була "уявіть, що у нас є щось, що ми називаємо" книгою ", в приміщенні-закритті," книга "- це копія там, у кутку, TAOCP, але на закритті столу. , це та копія книги "Дрезденські файли". Отже, залежно від того, в якому закритті ви знаходитесь, код "дай мені книгу" призводить до різних випадків ".
staticможна вважати функцію C з локальною змінною закриттям? Чи передбачають закриття в Хаскелл державі?
staticлокальною змінною у вас є саме одна).
Важко визначити, що таке закриття, не визначаючи поняття "держава".
В основному, з мовою з повним лексичним визначенням, яка розглядає функції як значення першого класу, відбувається щось особливе. Якби я робив щось на кшталт:
function foo(x)
return x
end
x = foo
Змінна xне тільки посилається, function foo()але також посилається на стан, яке fooбуло залишено в останній раз, коли воно поверталося. Справжня магія буває тоді, коли fooв її межах додатково визначені інші функції; це як власне міні-середовище (так само, як "нормально" ми визначаємо функції у глобальному середовищі).
Функціонально він може вирішити багато тих же проблем, що і "статичне" ключове слово C ++ (C?), Яке зберігає стан локальної змінної протягом багатьох викликів функцій; однак це більше схоже на те, щоб застосувати той самий принцип (статична змінна) до функції, оскільки функції - це значення першого класу; закриття додає підтримку збереження стану всієї функції (нічого спільного зі статичними функціями C ++).
Трактування функцій як значень першого класу та додавання підтримки для закриття також означає, що у вас може бути більше одного примірника однієї і тієї ж функції в пам'яті (подібно до класів). Це означає, що ви можете повторно використовувати той самий код, не скидаючи стан функції, як це потрібно при роботі зі C ++ статичними змінними всередині функції (можливо, це не так?).
Ось кілька тестів на підтримку закриття Lua.
--Closure testing
--By Trae Barlow
--
function myclosure()
print(pvalue)--nil
local pvalue = pvalue or 10
return function()
pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
print(pvalue)
pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
return pvalue
end
end
x = myclosure() --x now references anonymous function inside myclosure()
x()--nil, 20
x() --21, 31
x() --32, 42
--43, 53 -- if we iterated x() again
результати:
nil
20
31
42
Це може бути складним, і, ймовірно, варіюється від мови до мови, але в Луї здається, що кожного разу, коли виконується функція, її стан скидається. Я говорю це тому, що результати з наведеного вище коду були б іншими, якби ми отримували доступ до myclosureфункції / стану безпосередньо (замість анонімної функції вона повертається), як pvalueбуло б скинуто назад до 10; але якщо ми отримаємо доступ до стану мого закриття через x (анонімна функція), ви можете побачити, що pvalueвоно живе і добре десь у пам'яті. Я підозрюю, що в цьому є трохи більше, можливо, хтось може краще пояснити природу реалізації.
PS: Я не знаю лиза C ++ 11 (крім того, що в попередніх версіях), тому зауважте, що це не порівняння між закриттями в C ++ 11 та Lua. Крім того, всі 'лінії, проведені' від Lua до C ++, схожі на статичні змінні, а замикання не на 100% однакові; навіть якщо їх іноді використовують для вирішення подібних проблем.
Що я не впевнений у тому, що в прикладі коду вище, чи вважається анонімна функція або функція вищого порядку закриттям?
Закриття - це функція, яка має асоційований стан:
У perl ви створюєте такі закриття:
#!/usr/bin/perl
# This function creates a closure.
sub getHelloPrint
{
# Bind state for the function we are returning.
my ($first) = @_;a
# The function returned will have access to the variable $first
return sub { my ($second) = @_; print "$first $second\n"; };
}
my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");
&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World
Якщо ми подивимось на нову функціональність, що надається C ++.
Це також дозволяє прив’язати поточний стан до об'єкта:
#include <string>
#include <iostream>
#include <functional>
std::function<void(std::string const&)> getLambda(std::string const& first)
{
// Here we bind `first` to the function
// The second parameter will be passed when we call the function
return [first](std::string const& second) -> void
{ std::cout << first << " " << second << "\n";
};
}
int main(int argc, char* argv[])
{
auto hw = getLambda("Hello");
auto gw = getLambda("GoodBye");
hw("World");
gw("World");
}
Розглянемо просту функцію:
function f1(x) {
// ... something
}
Ця функція називається функцією вищого рівня, оскільки вона не вкладається в будь-яку іншу функцію. Кожна функція JavaScript пов'язує з собою список об'єктів, який називається "Сфера застосування" . Цей ланцюг сфери застосування є упорядкованим списком об'єктів. Кожен із цих об'єктів визначає деякі змінні.
У функціях верхнього рівня ланцюг області складається з одного об'єкта, глобального об'єкта. Наприклад, f1вищевказана функція має ланцюг області, в якій є один об'єкт, який визначає всі глобальні змінні. (зауважте, що термін "об'єкт" тут не означає об’єкт JavaScript, це лише об'єкт, визначений реалізацією, який діє як контейнер змінних, в якому JavaScript може "шукати" змінні.)
Коли ця функція викликається, JavaScript створює щось, що називається "об'єкт активації" , і ставить його у верхній частині ланцюга діапазону. Цей об'єкт містить усі локальні змінні (наприклад, xтут). Отже, у ланцюзі області у нас є два об'єкти: перший - об'єкт активації, а під ним - глобальний об'єкт.
Зауважте дуже уважно, що два об'єкти розміщуються у ланцюзі областей у різний час. Глобальний об'єкт ставиться, коли функція визначена (тобто, коли JavaScript розбирає функцію та створює об'єкт функції), а об’єкт активації вводиться, коли функція викликається.
Отже, тепер ми знаємо це:
Ситуація стає цікавою, коли ми маємо справу з вкладеними функціями. Отже, давайте створимо:
function f1(x) {
function f2(y) {
// ... something
}
}
Коли f1буде визначено, ми отримаємо для нього ланцюг області, що містить лише глобальний об'єкт.
Тепер, коли f1викликається, ланцюг області f1отримує об'єкт активації. Цей об'єкт активації містить змінну xта змінну, f2яка є функцією. І зауважте, що f2це визначається. Отже, на даний момент JavaScript також зберігає нову ланцюжок для роботи f2. Діаграма ланцюга, збережена для цієї внутрішньої функції, є діючою ланцюгом дії. Діючий чинний ланцюг сфери дії - це f1"s". Отже f2«s сфера ланцюг f1» и струму ланцюжок областей - яка містить об'єкт активації f1і глобальний об'єкт.
Коли f2його викликають, він отримує власний об'єкт активації, що містить y, доданий до його ланцюга діапазону, який вже містить об'єкт активації f1та глобальний об'єкт.
Якби була визначена інша вкладена функція, визначена в f2її ланцюзі областей, вона міститиме три об'єкти на час визначення (2 об'єкти активації двох зовнішніх функцій та глобальний об'єкт) та 4 на час виклику.
Отже, тепер ми розуміємо, як працює ланцюг сфери, але ми ще не говорили про закриття.
Поєднання об'єкта функції та області (набору змінних прив'язок), в якій вирішуються змінні функції, називається закриттям в літературі з інформатики - JavaScript остаточним посібником Девіда Фланагана
Більшість функцій викликаються за допомогою тієї ж ланцюга діапазону, яка діяла під час визначення функції, і це неважливо, що відбувається закриття. Закриття стають цікавими, коли вони викликаються в іншому ланцюжку, ніж той, що діяв, коли вони були визначені. Найчастіше це відбувається, коли вкладений об'єкт функції повертається з функції, в межах якої він був визначений.
Коли функція повертається, цей об'єкт активації видаляється з ланцюга області. Якщо не було вкладених функцій, більше немає посилань на об’єкт активації, і він збирається сміттям. Якщо були визначені вкладені функції, то кожна з цих функцій має посилання на ланцюг області, і ця ланцюг сфери відноситься до об'єкта активації.
Якщо ці об’єкти вкладених функцій залишилися в межах своєї зовнішньої функції, то вони самі будуть збирати сміття разом з об'єктом активації, про який вони посилалися. Але якщо функція визначає вкладену функцію і повертає її або зберігає її у властивості десь, тоді буде зовнішня посилання на вкладену функцію. Це не буде збирати сміття, і об’єкт активації, на який він посилається, також не буде зібраний сміття.
У нашому прикладі, ми не повертаємо f2з f1, отже, коли виклик f1повертається, його об'єкт активації буде вийнято з ланцюжка областей видимості і сміття збираються. Але якби у нас було щось подібне:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Тут повернення f2матиме ланцюг діапазону, який буде містити об’єкт активації f1, і, отже, він не буде збирати сміття. У цей момент, якщо ми зателефонуємо f2, він зможе отримати доступ f1до змінної, xнавіть якщо ми поза межами f1.
Звідси ми можемо бачити, що функція зберігає з нею ланцюг сфери дії, а з ланцюга області з'являються всі об'єкти активації зовнішніх функцій. У цьому суть закриття. Ми говоримо, що функції JavaScript мають "лексичний обсяг" , це означає, що вони зберігають область, яка була активною, коли вони були визначені на відміну від області, яка була активною при їх виклику.
Існує ряд потужних методів програмування, які передбачають закриття, такі як наближення приватних змінних, програмування, кероване подіями, часткове застосування тощо.
Також зауважте, що все це стосується всіх тих мов, які підтримують закриття. Наприклад, PHP (5.3+), Python, Ruby тощо.
Закриття - це оптимізація компілятора (також синтаксичний цукор?). Деякі люди називають це також об'єктом бідної людини .
Дивіться відповідь Еріка Ліпперта : (уривок нижче)
Компілятор генерує такий код:
private class Locals
{
public int count;
public void Anonymous()
{
this.count++;
}
}
public Action Counter()
{
Locals locals = new Locals();
locals.count = 0;
Action counter = new Action(locals.Anonymous);
return counter;
}
Мати сенс?
Також ви попросили порівняння. VB і JScript створюють закриття майже однаково.