Что такое закрытие?

Время от времени я вижу упоминание о «закрытиях», и я попытался найти его, но Wiki не дает объяснений, которые я понимаю. Может ли кто-нибудь помочь мне здесь?

146 голосов | спросил gablin 27 Jpm1000000pmThu, 27 Jan 2011 12:34:45 +030011 2011, 12:34:45

9 ответов


136

(Отказ от ответственности: это основное объяснение, поскольку это определение, я немного упрощаю)

Самый простой способ подумать о закрытии - это функция , которая может быть сохранена как переменная (называемая «функцией первого класса»), которая имеет специальную возможность доступа к другим переменные, локальные для области, в которой он был создан.

Пример (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-странице:

  1. Нажмите, чтобы перейти на черный
  2. Нажмите [enter], чтобы увидеть «true»
  3. Нажмите еще раз, измените цвет на белый.
  4. Нажмите [enter], чтобы увидеть «false»

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

Вызов setKeyPress состоит в том, чтобы продемонстрировать, как функция может передаваться точно так же, как любая переменная. область , сохраненная в закрытии, по-прежнему остается той, где определена функция.

Закрытия обычно используются в качестве обработчиков событий, особенно в JavaScript и ActionScript. Хорошее использование закрытий поможет вам неявно связывать переменные с обработчиками событий, не создавая обертки объектов. Тем не менее, неосторожное использование приведет к утечкам памяти (например, когда неиспользуемый, но сохраненный обработчик событий - это единственное, что может удержать большие объекты в памяти, особенно объекты DOM, предотвращающие сбор мусора).


1: На самом деле все функции JavaScript являются закрытием.

ответил Nicole 28 Jam1000000amFri, 28 Jan 2011 01:30:15 +030011 2011, 01:30:15
67

Закрытие - это просто другой способ взглянуть на объект. Объект - это данные, которые связаны с одной или несколькими функциями. Закрытие - это функция, которая связана с одной или несколькими переменными. Оба они в основном идентичны, по крайней мере, на уровне реализации. Реальная разница в том, откуда они взяты.

В объектно-ориентированном программировании вы объявляете класс объекта, определяя его переменные-члены и его методы (функции-члены) вверх, а затем создаете экземпляры этого класса. Каждый экземпляр поставляется с копией данных элемента, инициализированной конструктором. Затем вы имеете переменную типа объекта и передаете ее как часть данных, потому что основное внимание уделяется ее природе как данных.

В закрытии, с другой стороны, объект не определен заранее, как класс объекта, или создается через вызов конструктора в вашем коде. Вместо этого вы пишете замыкание как функцию внутри другой функции. Закрытие может относиться к любой из локальных переменных внешней функции, и компилятор обнаруживает это и перемещает эти переменные из пространства стека внешней функции в объявление скрытого объекта закрытия. Затем у вас есть переменная типа замыкания, и даже если это объект, находящийся под капотом, вы передаете его как ссылку на функцию, потому что фокус находится в его природе как функция.

ответил Mason Wheeler 28 Jam1000000amFri, 28 Jan 2011 01:48:58 +030011 2011, 01:48:58
28

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

Возьмем, например, определение функции 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)
}

Заметим, что функция без свободных переменных является частным случаем замыкания (с пустым множеством свободных переменных). Аналогично, анонимная функция - это особый случай анонимного закрытия , т. Е. Анонимная функция - анонимное закрытие без свободных переменных.

ответил Giorgio 6 22012vEurope/Moscow11bEurope/MoscowTue, 06 Nov 2012 14:08:32 +0400 2012, 14:08:32
9

Простое объяснение в 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.

Если вы запустите этот код, первая итерация присваивает значение 0 переменной closure и переписывает функцию:

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. 
ответил Muha 27 Jpm1000000pmThu, 27 Jan 2011 14:25:59 +030011 2011, 14:25:59
5

«Закрытие» - это, по существу, некоторое локальное состояние и некоторый код, объединенный в пакет. Как правило, локальное состояние происходит из окружающего (лексического) объема, а код (по существу) - это внутренняя функция, которая затем возвращается наружу. Закрытие представляет собой комбинацию захваченных переменных, которые видит внутренняя функция, и код внутренней функции.

Это одна из тех вещей, которые, к сожалению, немного сложно объяснить из-за незнания.

Одна из аналогий, которую я успешно использовал в прошлом, - «представьте, что у нас есть что-то, что мы называем« книгой », в закрытии комнаты« книга »- это копия там, в углу, TAOCP, но на закрытие таблицы, это то, что копия книги Дрезденских Файлов. Так что в зависимости от того, в каком закрытии вы находитесь, код «дайте мне книгу» приведет к разным событиям ».

ответил Vatine 27 Jpm1000000pmThu, 27 Jan 2011 13:23:16 +030011 2011, 13:23:16
5

Трудно определить, что замыкание не определяет понятие «состояние».

В принципе, на языке с полным лексическим охватом, который рассматривает функции как значения первого класса, происходит нечто особенное. Если бы я сделал что-то вроде:

function foo(x)
return x
end

x = foo

Переменная x не только ссылается на function foo(), но также ссылается на состояние foo, которое было оставлено в последний раз, когда оно было возвращено. Реальная магия возникает, когда foo имеет другие функции, дополнительно определенные в пределах своей области; это похоже на собственную мини-среду (так же, как «обычно» мы определяем функции в глобальной среде).

Функционально он может решить многие из тех же проблем, что и ключевое слово C static (C?) 'static', которое сохраняет состояние локальной переменной во многих вызовах функций; однако это больше похоже на применение того же принципа (статической переменной) к функции, поскольку функции являются значениями первого класса; закрытие добавляет поддержку для сохранения состояния всей функции (не имеет ничего общего с статическими функциями 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% одинаковы; даже если они иногда используются для решения подобных проблем.

То, что я не уверен в том, что в примере кода выше, считается ли анонимная функция или функция более высокого порядка закрытием?

ответил Vatine 27 Jpm1000000pmThu, 27 Jan 2011 13:23:16 +030011 2011, 13:23:16
4

Закрытие - это функция, которая имеет ассоциированное состояние:

В 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");
}
ответил Martin York 29 Jam1000000amSat, 29 Jan 2011 01:11:04 +030011 2011, 01:11:04
2

Рассмотрим простую функцию:

function f1(x) {
    // ... something
}

Эта функция называется функцией верхнего уровня, потому что она не вложена ни в какую другую функцию. Каждая функция JavaScript связывает с собой список объектов, называемых «Цепочная цепочка» . Эта цепочка областей представляет собой упорядоченный список объектов. Каждый из этих объектов определяет некоторые переменные.

В функциях верхнего уровня цепочка областей видимости состоит из одного объекта, глобального объекта. Например, функция f1 выше имеет цепочку областей видимости, в которой есть один объект, который определяет все глобальные переменные. (обратите внимание, что термин «объект» здесь не означает объект JavaScript, это просто объект, определенный реализацией, который действует как контейнер переменных, в котором JavaScript может «искать» переменные.)

Когда эта функция вызывается, JavaScript создает что-то, называемое «Объектом активации» , и помещает его в верхнюю часть цепочки областей видимости. Этот объект содержит все локальные переменные (например, x). Следовательно, теперь у нас есть два объекта в цепочке видимости: первый объект активации, а под ним - глобальный объект.

Обратите внимание, что два объекта помещаются в цепочку областей действия в разное время. Глобальный объект ставится, когда функция определена (т. Е. Когда JavaScript анализировал функцию и создал объект функции), и объект активации входит при вызове функции.

Итак, теперь мы это знаем:

  • Каждая функция имеет связанную с ней цепочку областей
  • Когда функция определена (когда объект функции создан), JavaScript сохраняет цепочку областей видимости с помощью этой функции
  • Для функций верхнего уровня цепочка областей видимости содержит только глобальный объект во время определения функции и добавляет дополнительный объект активации сверху во время вызова

Ситуация становится интересной, когда мы имеем дело с вложенными функциями. Итак, давайте создадим один:

function f1(x) {

    function f2(y) {
        // ... something
    }

}

Когда определяется f1, мы получаем цепочку областей видимости, содержащую только глобальный объект.

Теперь, когда вызывается f1, цепочка областей действия f1 получает объект активации. Этот объект активации содержит переменную x и переменную f2, которая является функцией. И, обратите внимание, что f2 определяется. Следовательно, на этом этапе JavaScript также сохраняет новую цепочку цепочки для f2. Целевая цепочка, сохраненная для этой внутренней функции, - это действующая целая цепочка областей действия. Текущая цепочка цепочки по существу соответствует цепочке f1. Следовательно, цепочка областей f2 - это цепочка цепей f1 current , которая содержит объект активации f1 и глобальный объект.

Когда вызывается f2, он получает свой собственный объект активации, содержащий y, добавленный в свою цепочку областей видимости, которая уже содержит объект активации f1 и глобальный объект.

Если в f2 была определена другая вложенная функция, то ее цепочка цепей будет содержать три объекта во время определения (2 объекта активации из двух внешних функций и глобальный объект) и 4 при вызове время.

Итак, теперь мы понимаем, как работает цепочка цепей, но мы еще не говорили о закрытии.

  

Комбинация функционального объекта и области видимости (набор переменных привязок), в которой разрешены переменные функции, называется замыканием в литературе по информатике - JavaScript окончательное руководство David Flanagan

Большинство функций вызывается с использованием той же цепочки областей действия, которая действовала, когда функция была определена, и действительно не имеет значения, что есть закрытие. Замыкания становятся интересными, когда они вызываются в другой цепочке видимости, чем те, которые действовали, когда они были определены. Эта происходит чаще всего, когда вложенный объект функции возвращается из функции в пределах которого он был определен.

Когда функция возвращается, этот объект активации удаляется из цепочки областей видимости. Если не было вложенных функций, ссылок на объект активации больше нет, и он собирает мусор. Если были определены вложенные функции, то каждая из этих функций имеет ссылку на цепочку областей видимости и что цепочка областей видимости относится к объекту активации.

Если эти объекты вложенных функций остались в пределах их внешней функции, однако, они сами будут собирать мусор вместе с объектом активации, на который они ссылались. Но если функция определяет вложенную функцию и возвращает ее или сохраняет ее в каком-либо свойстве, тогда будет внешняя ссылка на вложеннуюфункция. Это не будет собирать мусор, и объект активации, на который он ссылается, не будет собирать мусор.

В нашем примере выше мы не возвращаем f2 из f1, поэтому, когда возвращается вызов f1, его объект активации будут удалены из сферы действия и собранного мусора. Но если бы у нас было что-то вроде этого:

function f1(x) {

    function f2(y) {
        // ... something
    }

    return f2;
}

Здесь возвращаемый f2 будет иметь цепочку областей видимости, которая будет содержать объект активации f1, и, следовательно, он не будет собираться мусором. На этом этапе, если мы назовем f2, он сможет получить доступ к переменной f1 x, даже если мы вне f1.

Следовательно, мы можем видеть, что функция сохраняет свою цепочку видимости с ней, а с цепочкой областей видимости - все объекты активации внешних функций. В этом суть закрытия. Мы говорим, что функции в JavaScript «лексически ограничены» , что означает, что они сохраняют область действия, которая была активной, когда они были определены в отличие от области действия, которая была активной при их вызове.

Существует ряд мощных методов программирования, которые включают замыкания, такие как приближение частных переменных, программирование, управляемое событиями, частичное приложение и т. д.

Также обратите внимание, что все это относится ко всем тем языкам, которые поддерживают закрытие. Например, PHP (5.3+), Python, Ruby и т. Д.

ответил treecoder 6 22012vEurope/Moscow11bEurope/MoscowTue, 06 Nov 2012 18:10:47 +0400 2012, 18:10:47
-1

Закрытие - это оптимизация компилятора (ака синтаксический сахар?). Некоторые люди ссылаются на это как Объект Бедного Человека .

См. ответ Эрика Липперта : (выдержка ниже)

Компилятор будет генерировать код следующим образом:

 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 оба создают закрытие почти таким же образом.

ответил treecoder 6 22012vEurope/Moscow11bEurope/MoscowTue, 06 Nov 2012 18:10:47 +0400 2012, 18:10:47

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132