Что такое "Закрытие"?

Я задал вопрос о Currying и закрытиях. Что такое закрытие? Как это относится к каррированию?

367
31 авг. '08 в 7:38
источник поделиться
18 ответов

Переменная область

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

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 находится в закрытии.

600
19 сент. '11 в 0:04
источник

Связанные вопросы


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

Я приведу пример (в JavaScript):

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

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Что делает эта функция makeCounter, она возвращает функцию, которую мы назвали x, которая будет подсчитывать по одному при каждом ее вызове. Поскольку мы не предоставляем никаких параметров х, он должен как-то помнить счет. Он знает, где его найти, исходя из того, что называется лексическим охватом - оно должно смотреть на то место, где оно определено, чтобы найти значение. Это "скрытое" значение - это то, что называется замыканием.

Вот мой пример currying:

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

var add3 = add(3);

add3(4); returns 7

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

84
31 авг. '08 в 7:49
источник

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

54
31 авг. '08 в 12:08
источник

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

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
26
31 авг. '08 в 7:54
источник

Прежде всего, вопреки тому, что говорит вам большинство людей, закрытие не является функцией! Так что это?
Это набор символов, определенных в функции "окружающий контекст" (известный как ее среда), которые делают его CLOSED-выражением (то есть выражением, в котором каждый символ определен и имеет значение, поэтому его можно оценить).

Например, когда у вас есть функция 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`]
}

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

Подробнее об этой теории здесь: qaru.site/questions/4901/...

Стоит отметить, что в приведенном выше примере функция-обертка возвращает свою внутреннюю функцию как значение. Момент, который мы называем этой функцией, может быть удален во времени с момента, когда функция была определена (или создана). В частности, его функция обертки больше не работает, а ее параметры, которые были в стеке вызовов, больше не существуют: P Это создает проблему, потому что внутренняя функция нуждается в y, чтобы быть там, когда она вызывается! Другими словами, для того, чтобы как-то пережить функцию обертки, необходимо, чтобы переменные были закрыты, и там, где это необходимо. Поэтому внутренняя функция должна сделать моментальный снимок этих переменных, которые делают его закрытие и сохраняют их где-то в безопасности для последующего использования. (Где-то вне стека вызовов.)

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

26
27 апр. '16 в 5:33
источник

Чтобы облегчить понимание закрытий, было бы полезно изучить, как они могут быть реализованы на процедурном языке. Это объяснение будет следовать упрощенной реализации замыканий на Схеме.

Чтобы начать, я должен ввести понятие пространства имен. Когда вы вводите команду в интерпретатор Схемы, она должна оценивать различные символы в выражении и получать их значение. Пример:

(define x 3)

(define y 4)

(+ x y) returns 7

Определяющие выражения сохраняют значение 3 в пятне для x и значение 4 в пятне для y. Затем, когда мы вызываем (+ x y), интерпретатор просматривает значения в пространстве имен и может выполнять операцию и возвращать 7.

Однако на Схеме есть выражения, которые позволяют временно переопределить значение символа. Вот пример:

(define x 3)

(define y 4)

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

x returns 3

То, что делает ключевое слово let, представляет новое пространство имен с x как значение 5. Вы заметите, что он все еще может видеть, что y равно 4, что возвращает сумму, равную 9. Вы также можете увидеть, что после выражения закончил x вернулся к состоянию 3. В этом смысле x временно маскируется локальным значением.

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

Как это реализовать? Простым способом является связанный список - голова содержит новое значение, а хвост содержит старое пространство имен. Когда вам нужно искать символ, вы начинаете во главе и прокладываете себе путь по хвосту.

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

(define x 3)

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

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

Определим x как 3, а плюс-x - его параметр, y, плюс значение x. Наконец, мы называем плюс-x в среде, где x был замаскирован новым x, этот оценивается 5. Если мы просто сохраняем операцию (+ xy) для функции plus-x, поскольку мы находимся в контексте из x будет 5, результат будет равен 9. Это то, что называется динамическим охватом.

Однако, Scheme, Common Lisp и многие другие языки имеют то, что называется лексическим охватом - помимо хранения операции (+ x y) мы также сохраняем пространство имен в этой конкретной точке. Таким образом, когда мы просматриваем значения, мы можем видеть, что x в этом контексте действительно 3. Это замыкание.

(define x 3)

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

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

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

23
31 авг. '08 в 19:30
источник

Вот пример реального мира, почему Closures подталкивает задницу... Это прямо из моего кода 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 с 5000 ms, первый фрагмент запускается и сохраняет переданные аргументы в его закрытии. Затем через 5 секунд, когда происходит обратный вызов setTimeout, закрытие все еще сохраняет эти переменные, поэтому оно может вызывать исходную функцию с исходными параметрами.
Это тип currying или функция украшения.

Без замыканий вам придется каким-то образом поддерживать эти переменные за пределами функции, таким образом забивая код вне функции чем-то, что логически принадлежит внутри него. Использование закрытий может значительно улучшить качество и читаемость вашего кода.

10
07 нояб. '10 в 22:04
источник

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

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

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
}

источник: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

5
07 февр. '16 в 16:38
источник

ТЛ; др

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

Подробное объяснение стиля Википедии

Согласно Википедии, закрытие:

Методы реализации привязки лексически ограниченных имен в языках с первоклассными функциями.

Что это значит? Давайте посмотрим на некоторые определения.

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

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 x связан с 1 или 5 (в зависимости от замыкания).

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

Лексический обзор

Как говорит Википедия, сфера:

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

Есть две техники:

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

Для более подробного объяснения проверьте этот вопрос и посмотрите на Википедию.

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

Заворачивание

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

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

Карринг

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

5
05 июля '17 в 2:43
источник

В нормальной ситуации переменные связаны по правилу определения области видимости: локальные переменные работают только внутри определенной функции. Закрытие - это способ временного нарушения этого правила для удобства.

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.

4
31 авг. '08 в 8:31
источник

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

.

2
07 сент. '13 в 16:50
источник

Закрытие - это функция в JavaScript, где функция имеет доступ к своим собственным переменным области видимости, доступ к внешним переменным функции и доступ к глобальным переменным.

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

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

******************
Example of Closure
******************

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

1
18 апр. '19 в 23:20
источник

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

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
03 авг. '17 в 12:15
источник

Если вы находитесь в мире Java, вы можете сравнить замыкание с функцией-членом класса. Посмотрите на этот пример

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

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

0
02 апр. '16 в 22:18
источник

Вот еще один пример реальной жизни и использование языка скриптов, популярного в играх - 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 исчезает, когда этот блок кода заканчивает его область видимости (потому что он локальный), однако это значение было заключено в закрытие, поэтому новая переопределенная функция dofile может получить к ней доступ, или, скорее, копию, сохраненную вместе с как "upvalue".

0
12 мая '11 в 8:57
источник

Из Lua.org:

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

0
31 дек. '16 в 2:06
источник

Пожалуйста, посмотрите код ниже, чтобы понять замыкание более глубоко:

        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);          
        }

Позвольте мне просто объяснить, что когда созданная функция ничего не происходит, пока она не вызовет цикл for в 1-м коде, который вызывается 5 раз, но не вызывается сразу, поэтому, когда он вызывается, т.е. Через 1 секунду, а также это происходит асинхронно, так что до этого цикл завершается и сохраняется значение 5. в var я и, наконец, выполнить функцию 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 в памяти, но его область видимости всегда видна в его 5,5,5,5,5 функции, поэтому, когда функция выполняется внутри setTimeout пять раз, она печатает 5,5,5,5,5

поэтому, чтобы решить эту проблему, используйте IIFE, как описано выше.

0
15 дек. '18 в 18:38
источник

Curry: позволяет частично оценить функцию, передав только подмножество ее аргументов. Учти это:

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
04 мая '19 в 6:16
источник

Посмотрите другие вопросы по меткам или Задайте вопрос