Час від часу я бачу, як згадуються "закриття", і я намагався шукати це, але 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 створюють закриття майже однаково.