Время от времени я вижу упомянутое «замыкание», и я пытался найти его, но Вики не дает объяснения, которое я понимаю. Может ли кто-нибудь помочь мне здесь?
Время от времени я вижу упомянутое «замыкание», и я пытался найти его, но Вики не дает объяснения, которое я понимаю. Может ли кто-нибудь помочь мне здесь?
Ответы:
(Отказ от ответственности: это базовое объяснение; что касается определения, я немного упрощаю)
Самый простой способ думать о замыкании - это функция, которая может храниться как переменная (называемая «функцией первого класса»), которая имеет специальную возможность доступа к другим переменным, локальным для области, в которой она была создана.
Пример (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
является свободным по отношению к функции, 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
локальной переменной считаться замыканием? Завершения в Хаскеле связаны с государством?
static
локальной переменной, у вас точно одна).
Трудно определить, что такое замыкание, не определяя понятия «государство».
По сути, в языке с полной лексической областью видимости, который рассматривает функции как значения первого класса, происходит нечто особенное. Если бы я сделал что-то вроде:
function foo(x)
return x
end
x = foo
Переменная x
не только ссылается, function foo()
но и ссылается на состояние, foo
оставленное в последний раз, когда она возвращалась. Настоящая магия случается, когда foo
другие функции дополнительно определены в ее рамках; это похоже на собственную мини-среду (как обычно, мы определяем функции в глобальной среде).
Функционально он может решить многие из тех же проблем, что и ключевое слово «static» в 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
Это может быть сложно, и это, вероятно, варьируется от языка к языку, но в Lua кажется, что всякий раз, когда функция выполняется, ее состояние сбрасывается. Я говорю это потому, что результаты из приведенного выше кода были бы другими, если бы мы обращались к myclosure
функции / состоянию напрямую (а не через анонимную функцию, которую она возвращает), как pvalue
было бы возвращено к 10; но если мы получим доступ к состоянию myclosure через 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
, цепочка областей видимости - 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 создают замыкания практически одинаково.