Что такое замыкание в программировании
Сегодня разбираем полезное понятие из мира программирования — замыкание. Это нужно тем, кто хочет серьёзно заниматься разработкой и говорить со старшими товарищами на одном языке.
Для начала потребуется два термина: область видимости и стек. Если нужно вспомнить, раскрывайте:
Что такое область видимости
Область видимости определяет, к каким переменным функция, команда или другая переменная может получить доступ, а к каким нет. Проще говоря, что каждая функция «видит» — видит ли она те переменные и объекты, которые созданы за её пределами? Видит ли она то, что вложено в функции, которые запускаются внутри неё?
Обычно так: если переменная объявлена в самой программе, к ней можно получить доступ из вложенной функции. А если объявить переменную внутри функции, то не обратиться к ней извне не получится.
Что такое стек
Стек — это как список задач при выполнении программы. Сначала компьютер исполняет один код, внутри которого появляется какая-то функция — это как будто отдельная программа. Компьютер откладывает текущую программу, делает себе в стеке пометку «вернуться сюда, когда доделаю новую программу» и исполняет эту новую функцию. Исполнил — смотрит в стек, куда вернуться. Возвращается туда.
Про стек нужно думать, потому что он не безразмерный. Нельзя бесконечно вкладывать функции друг в друга.
Что такое замыкание
Замыкание в программировании — это когда одна функция возвращает как результат своей работы не переменную, а другую функцию. При этом хитрость в том, что внутренняя функция имеет доступ к переменным из внешней функции и может с ними работать в любой момент.
Чаще всего это используют, чтобы сделать переменную, которая на самом деле работает как функция.
Звучит сложно, объясним на примерах.
Пример замыкания в коде
Сделаем функцию, которая вернёт нам фразу «Привет, …» вместе с именем, которое мы в него отправим. Но сделаем это через замыкание — чтобы посмотреть, как именно всё устроено.
Обратите внимание на две переменные в конце: mike и maxim. По сути, эти переменные — ссылки на вызов результата функции hello(), но с конкретным параметром.
// внешняя функция function hello(name) < // внутренняя функция-замыкание // возвращаем её как результат работы внешней return function() < // внутренняя функция выводит сообщение на экран console.log("Привет, " + name); >> // создаём новые переменные, используя замыкание mike = hello("Миша"); maxim = hello("Максим") // запускаем внутреннюю функцию с нашими параметрами, просто указав имена переменной mike(); maxim();
Ещё один пример, посложнее
Только что мы сделали просто — привязали сообщение к переменной без возможности на него повлиять. Но можно создавать такие переменные, у которых могут быть свои параметры — и в них уже передавать любые значения.
Для примера сделаем замыкание и заведём две переменные — каждая будет выдавать сообщение со своим началом фразы, а продолжение будем передавать им в виде параметра:
// внешняя функция, у которой есть свой параметры function greeting(hi) < // внутренняя функция-замыкание, тоже со своим параметром // возвращаем её как результат работы внешней return function(name) < // внутренняя функция выводит сообщение на экран console.log(hi + ", " + name); >> // создаём новые переменные, используя замыкание morning = greeting("Доброе утро"); thnx = greeting("Спасибо за комментарий") // запускаем внутреннюю функцию с нашими параметрами, указав параметры вызова morning("коллеги"); thnx("Павел");
Причём здесь область видимости
Из последнего примера видно, что объявление переменных происходит так:
- переменной присваивается результат выполнения внешней функции;
- этот результат зависит от параметра, который мы ей передали;
- потом, при обращении к переменной, происходит обработка только второго параметра, а первый хранится где-то в памяти и подставляется в нужный момент.
Но как такое может быть, когда внутренняя функция использует переменную из области видимости внешней функции, которой уже нет?
Всё дело в том, что при замыкании создаётся как бы виртуальное окружение, в котором и хранится значение из внешней функции. А вот как это работает с точки зрения области видимости:
Зачем нужны замыкания
На замыканиях строится около половины алгоритмов в функциональном программировании. А ещё на них можно построить много разного:
- изолировать логику выполнения фрагментов кода, если это не позволяют сделать встроенные возможности языка (как в JavaScript);
- лучше структурировать код, особенно при организации функций, которые отличаются только несколькими элементами;
- реализовать инкапсуляцию в тех языках, где её нет.
Что дальше
Следующий шаг — попробовать замыкания в деле. Напишем небольшой код и проверим, как работают замыкания и для чего они могут пригодиться.
Реализации алгоритмов/Замыкание
Замыкание (англ. closure ) в программировании — функция, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своём контексте.
Реализации замыканий и их аналогов на различных языках программирования [ править ]
Delphi [ править ]
Пример работы замыканий на Delphi (c 2009 версии):
type TGenericFunction = reference to function: string; function Factory(const ASomeText: string): TGenericFunction; begin Result := function: string begin Result := ASomeText; end; end; var f1, f2: TGenericFunction; begin f1 := Factory('First'); f2 := Factory('Second'); Writeln(f1); Writeln(f2); Readln; end.
В версиях начиная с 2009, этот код выведет в консоль строки First и Second. Когда переменной типа reference to *** присваивается совместимая по спецификации анонимная подпрограмма или метод, неявно создаётся и инициализируется экземпляр анонимного класса, с полями для хранения значений, используемых подпрограммой из контекста её объявления, методом выполнения (присвоенной подпрограммой) и счётчиком ссылок.
Scheme [ править ]
Пример работы замыканий на Scheme:
(define (make-adder n) ; возвращает замкнутое лямбда-выражение (lambda (x) ; в котором x - связанная переменная, (+ x n))) ; а n - свободная (захваченная из внешнего контекста) (define add1 (make-adder 1)) ; делаем процедуру для прибавления 1 (add1 10) ; возвращает 11 (define sub1 (make-adder -1)); делаем процедуру для вычитания 1 (sub1 10) ; возвращает 9
C# [ править ]
Анонимные методы в C# 2.0 могут замыкаться на локальный контекст:
int[] ary = 1, 2, 3 >; int x = 2; var ary1 = Array.ConvertAllint, int>(ary, delegate(int elem) return elem * x; >); // // or.. var ary2 = Array.ConvertAllint, int>(ary, elem => elem * x); //
Функция Array.ConvertAll преобразует один список/массив в другой, применяя для каждого элемента передаваемую ей в качестве параметра функцию.
В C# 3.0 введены лямбда-выражения, которые делают синтаксис анонимных методов более кратким и выразительным. Соответственно, они также поддерживают замыкания. То есть, замыкания в C# 3.0 практически аналогичны анонимным функциям из C# 2.0, но синтаксически более кратки. Вот тот же пример с применением лямбда-выражений в C# 3.0:
int[] ary = 1, 2, 3 >; var x = 2; var ary1 = ary.Select(elem => elem * x); //
Метод Select аналогичен методу Array.ConvertAll за тем исключением, что он принимает и возвращает IEnumerable.
C++ [ править ]
В языке C++ замыкание долгое время не поддерживалось. Однако стандарт языка C++11 вводит лямбда-функции и выражения, ограниченно поддерживающие замыкание:
functionfunctionint()>()> f = [] int x = 0; return [=] () mutable return ++x; >; >; auto fun = f(); for (int i = 0; i 5; ++i) cout <fun() <endl; >
VB.NET [ править ]
В VB.NET 9.0 лямбда-функции могут быть только однострочными. Начиная с версии 10.0, можно использовать синтаксис для описания многострочных лямбда-функций.
Dim ary As Integer() = 1, 2, 3> Dim x As Integer = 2 ' VB.NET 9.0 - Dim ary1() As Integer = Array.ConvertAll(Of Integer, Integer)(ary, Function(elem) elem * x) ' VB.NET 10.0 - Dim ary2() As Integer = Array.ConvertAll(Of Integer, Integer)(ary, Function(elem) Return elem * x End Function)
Ruby [ править ]
Некоторые языки, такие как Ruby, позволяют выбирать различные способы замыканий по отношению к оператору возврата return :
# ruby def foo f = Proc.new return "return from foo from inside proc" > f.call # после вызова функции замыкания f осуществляется выход из foo # результатом работы функции foo является результат работы f замыкания return "return from foo" end def bar f = lambda return "return from lambda" > f.call # после вызова функции замыкания f продолжается выполнение bar return "return from bar" end puts foo # печатает "return from foo from inside proc" puts bar # печатает "return from bar"
И Proc.new , так же как и lambda , в этом примере — это способы создания замыкания, но семантика замыканий различна по отношению к оператору return .
PHP [ править ]
PHP имеет встроенную поддержку замыканий начиная с версии 5.3. Пример замыкания. Локальная переменная $id будет увеличиваться при вызове возвращаемой функцией getAdder вложенной функции:
function getAdder() $id = 1; return function() use (&$id) // use (&$id) для того чтобы передать в возвращаемую функцию внешнюю переменную $id return $id++; >; > $test= getAdder(); echo $test(); //1 $id увеличивается только после того, как возвращается, так как написано $id++ echo $test(); //2 echo $test(); //3 echo $test(); //4
Для более ранних версий возможно использовать одноименный шаблон проектирования, который реализуется в библиотеке Николаса Нассара. P.S. Однако, до сих пор существует проблема с замыканиями в классах, в частности — для статических методов класса.
Java [ править ]
Java реализует концепцию замыкания с помощью анонимных классов. Анонимный класс имеет доступ к полям класса, в лексическом контексте которого он определён, а также к переменным с модификатором final в лексическом контексте метода.
class CalculationWindow extends JFrame private JButton btnSave; . public final void calculateInSeparateThread(final URI uri) // Выражение "new Thread() < . >" представляет собой пример анонимного класса. new Thread() public void run() // Имеет доступ к финальным (final) переменным: calculate(uri); // Имеет доступ к приватным членам содержащего класса: btnSave.setEnabled(true); > >.start(); > >
Python [ править ]
Пример с использованием замыканий и каррирования:
# Реализация с помощью именованных функций: def taskerize(func_object): def unbound_closure(*args, **kwarg): def bound_closure(): return func_object(*args, **kwarg) return bound_closure return unbound_closure # Равносильная реализация с использованием lambda: taskerize = lambda func_object: ( lambda *args, **kwarg: ( lambda: func_object(*args, **kwarg) ) ) @taskerize # применение декоратора равнозначно записи testfunc = taskerize(testfunc) после объявления функции. def testfunc(a, b, c): return a + b * c f = testfunc(1, 2, 3) print f() # выведет 7
Пример простого замыкания:
# Реализация с помощью именованных функций: def make_adder(x): def adder(n): return x + n # захват переменной "x" из внешнего контекста return adder # То же самое, но через безымянные функции: make_adder = lambda x: ( lambda n: x + n ) f = make_adder(10) print f(5) # 15 print f(-1) # 9
# Функция с кучей аргументов (26 шт.), делающая что-то невразумительное. def longfunc(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z): print 'Меня вызвали с такими аргументами: ', a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z return a + b * c - d / e + f / g - h * i - (j * (k - l) + m) + (n * o) / (p - q + r) + (s * (t + (u * (v + w)))) - (x * y * z) def curry(func_object, *args): def innerfunc(*local_args): # в функции выполняется замыкание на args и func_object из внешнего контекста return func_object(*(args + local_args)) # а еще нам нужно прилепить в конец тех аргументов, что у нас были, новые return innerfunc # По уже сложившейся традиции — то же самое, только лямбдами: curry = lambda func_object, *args: ( lambda *local_args: ( func_object( *(args + local_args) ) ) ) # "достраиваем" функцию, как пожелаем. f1 = curry(longfunc, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100) f2 = curry(f1, 110, 120, 130, 140) f3 = curry(f2, 150, 160, 170, 180, 190, 200) f4 = curry(f3, 210) # не обязательно использовать функцию, к которой было применено каррирование, только один раз. f5 = curry(f4, 220, 230, 240, 250, 260) # раз f5b = curry(f4, 220, 230, 240, 250) # два! f6b = curry(f5b, 260) print f5() # выведет 2387403 print f6b() # опять выведет 2387403 # контроль того, что каррирование всё сделало верно (вызываем функцию со всеми её 26-ю параметрами): print longfunc( # перенос значений аргументов функций на несколько строк не имеет ничего общего с каррированием. Нет, правда. 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260 ) # да, опять выведет 2387403.
OCaml [ править ]
В следующем интерактивном примере (add 5) является замыканием, так как содержит как «функцию» (add x), так и «окружение» (x = 5) [1] :
# let add x = (fun y -> x + y) ;; val add : int -> int -> int = fun> # (add 5) 3 ;; - : int = 8
JavaScript [ править ]
В JavaScript областью видимости локальных переменных (объявляемых словом var) является тело функции, внутри которой они определены.
Если вы объявляете функцию внутри другой функции, первая получает доступ к переменным и аргументам последней:
function outerFn(myArg) var myVar; function innerFn() // имеет доступ к myVar и myArg > >
При этом, такие переменные продолжают существовать и остаются доступными внутренней функции даже после того, как внешняя функция, в которой они определены, была исполнена.
Рассмотрим пример — функцию, возвращающую количество собственных вызовов:
function createCounter() var numberOfCalls = 0; return function() return ++numberOfCalls; > > var fn = createCounter(); fn(); // 1 fn(); // 2 fn(); // 3
Только после удаления переменной fn, которая ссылается на возвращенную функцию, переменная numberOfCalls будет удалена сборщиком мусора.
Perl [ править ]
Пример с использованием замыканий на Perl:
# возвращает анонимную функцию sub adder($) my $x = shift(); # в котором x - свободная переменная, return sub ($) my $y = shift(); # а y - связанная переменная return $x + $y; >; > $add1 = adder(1); # делаем процедуру для прибавления 1 print $add1->(10); # печатает 11 $sub1 = adder(-1); # делаем процедуру для вычитания 1 print $sub1->(10); # печатает 9
Lua [ править ]
Пример с использованием замыканий на Lua:
function makeaddfunc(x) -- Возвращает новую анонимную функцию, которая добавляет x к аргументу return function(y) -- Когда мы ссылаемся на переменную x, которая вне текущей области, -- и время жизни которой меньше, чем этой анонимной функции, -- Lua создаёт замыкание. return x + y end end plustwo = makeaddfunc(2) print(plustwo(5)) -- Выводит 7
Haskell [ править ]
В Haskell замыкания используются повсеместно в виде частичного применения аргументов к функциям (также известного как каррирование).
sum3 :: Int -> Int -> Int -> Int sum3 x y z = x + y + z
Определение функции «sum3» напоминает следующий код на C:
int sum3(int x, int y, int z) return(x + y + z); >
На самом деле «sum3» эквивалентна функции «sum3_desugared», по определению которой видно, что «sum3_desugared» принимает один аргумент «x» и возвращает новую функцию со связанной переменной «x». Новая функция также принимает только один аргумент «y» и возвращает функцию от одного аргумента «z».
sum3_desugared :: Int -> Int -> Int -> Int sum3_desugared = \x -> \y -> \z -> x + y + z
Псевдоопределение таких функций выглядит следующим образом («bounded» — это некоторые фиксированные значения, которые неявно хранятся вместе с функциями):
sum2_closure :: Int -> Int -> Int sum2_closure = \y -> \z -> bounded_from_sum3 + y + z sum1_closure :: Int -> Int sum1_closure = \z -> bounded_from_sum3 + bounded_from_sum2 + z sum_value :: Int sum_value = bounded_from_sum3 + bounded_from_sum2 + bounded_from_sum1 sum2_with42 = sum3 42 sum2_with42 = \y -> \z -> 42 + y + z sum1_with42_with13 = sum3 42 13 sum1_with42_with13 = sum2_with42 13 sum1_with42_with13 = \z -> 42 + 13 + z sum_with42_with13_with66 = sum3 42 13 66 sum_with42_with13_with66 = sum2_with42 13 66 sum_with42_with13_with66 = sum1_with42_with13 66 sum_with42_with13_with66 = 42 + 13 + 66
Такой подход очень часто применяется для создания «специализированных» функций из более общих:
— (&&) :: Bool -> Bool -> Bool — liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c ( ) (Monad m) => m Bool -> m Bool -> m Bool ( ) = liftM2 (&&) — foldr :: (a -> b -> b) -> b -> [a] -> b custom_fold :: [a] -> b custom_fold = foldr k z where z = k x z =
Для удобства использования общих функций рекомендуют располагать в них параметры в порядке от общих к частным. Например, сначала принимать функцию-обработчик перед самими данными.
Smalltalk [ править ]
Пример с использованием замыкания на Smalltalk:
createClosureWithComparator: aComparator ^[ :each | ^ each aComparator ]
Выполнение метода создает замыкание, при использовании которого будет происходить сравнение произвольного аргумента each и связанного значения aComparator.
MATLAB [ править ]
Пример реализации замыкания в MATLAB с использованием вложенных функций:
function d = some_func(a) function c = nested_func(b) c = a + b; end d = @nested_func; end >> f = some_func(10); f = @some_func/nested_func >> f(5) ans = 15
Пример реализации замыкания в MATLAB с использованием анонимных функций:
>> f = @(x) @(y) x + y f = @(x)@(y)x+y >> ff = f(10) ff = @(y)x+y >> ff(5) ans = 15
Objective-C [ править ]
Пример реализации замыкания в Objective-c с использованием блоков (blocks):
typedef int (^Add)(); - (Add)addFunction __block int i = 0; Add block = ^ return i++; >; return block; > int main(int argc, char * argv[]) Add block = [self addFunction]; NSLog(@"%i", block()); NSLog(@"%i", block()); NSLog(@"%i", block()); > >>0 >>1 >>2
Common LISP [ править ]
(defun photon-energy-common (planck) (lambda (freq) (* planck freq))) (setq photon-energy-hbar (photon-energy-common 1.054571726E-23)) (setq photon-energy-h (photon-energy-common 6.62606957E-23)) (funcall photon-energy-h 10E12)
Go [ править ]
package main import "fmt" func fibonacci() func() int a, b := 0, 1 return func() int a, b = b, a + b return b > > func main() f := fibonacci() for i := 0; i 10; i++ fmt.Println(f()) > >
Visual Prolog [ править ]
F = <(Y) = <(X)=X+Y>>, G = F(2), write(G(3)), % 5 write(G(8)) % 10
Swift [ править ]
// Вариант 1 reversed = sorted(names, (s1: String, s2: String) -> Bool in return s1 > s2 >) // Вариант 2 reversed = sorted(names, (s1: String, s2: String) -> Bool in return s1 > s2 > ) // Вариант 3 reversed = sorted(names, s1, s2 in return s1 > s2 > ) // Вариант 4 reversed = sorted(names, s1, s2 in s1 > s2 > ) // Вариант 5 reversed = sorted(names, $0 > $1 > ) // Вариант 6 reversed = sorted(names) $0 > $1 >
Примечания [ править ]
- ↑OCaml Closures and Currying. CMSC 330, Summer 2009
Замыкание (программирование)
Текущая версия страницы пока не проверялась опытными участниками и может значительно отличаться от версии, проверенной 19 февраля 2015 года; проверки требует 31 правка.
Текущая версия страницы пока не проверялась опытными участниками и может значительно отличаться от версии, проверенной 19 февраля 2015 года; проверки требует 31 правка.
У этого термина существуют и другие значения, см. Замыкание.
Эта статья или раздел нуждается в переработке.
Пожалуйста, улучшите статью в соответствии с правилами написания статей.
Замыкание (англ. closure ) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.
Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.
Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.
В случае замыкания ссылки на переменные внешней функции действительны внутри вложенной функции до тех пор, пока работает вложенная функция, даже если внешняя функция закончила работу, и переменные вышли из области видимости. [1]
Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.
Примеры [ править | править код ]
Больше примеров смотрите в викиучебнике.
В языке Scheme [ править | править код ]
(define (make-adder n) ; возвращает замкнутое лямбда-выражение (lambda (x) ; в котором x - связанная переменная, (+ x n) ; а n - свободная (захваченная из внешнего контекста) ) ) (define add1 (make-adder 1)) ; делаем процедуру для прибавления 1 (add1 10) ; вызываем её, возвращает 11 (define sub1 (make-adder -1)); делаем процедуру для вычитания 1 (sub1 10) ; вызываем её, возвращает 9
В языке JavaScript [2] [ править | править код ]
'use strict'; const add = function(x) return function(y) const z = x + y; console.log(x + '+' + y + '=' + z); return z; >; >; const res = add(3)(6); // вернёт 9 и выведет в консоль 3+6=9 console.log(res);
Этот же код в версии ECMAScript2015 с использованием «стрелочных функций»:
'use strict'; const add = x => y => const z = x + y; console.log(x + '+' + y + '=' + z); return z; >; const res = add(3)(6); // вернёт 9 и выведет в консоль 3+6=9 console.log(res);
Пояснение: в JavaScript сочетание => является оператором объявления стрелочной функции, см например
'use strict'; const add = x => y => const z = x + y; console.log(x + '+' + y + '=' + z); return add(z); >; const res = add(1)(4)(6)(9); console.log(res); /* 1+4=5 5+6=11 11+9=20 [Function]*/
Когда JS-код работает — локальные переменные хранятся в scope. В JavaScript локальные переменные могут оставаться в памяти даже после того, как функция вернула значение.
Все функции в JavaScript это замыкания, то есть всегда, когда создается функция — всегда создается замыкание, хоть и зачастую оно пустое, так как функции обычно из объявления контекста как правило ничего не используют. Но нужно понимать разницу между созданием замыкания и созданием нового scope-объекта: замыкание (функция + ссылка на текущую цепочку scope-объектов) создается при определении функции, но новый scope-объект создается (и используется для модификации цепочки scope-объектов замыкания) при каждом вызове функции.
В языке PHP [ править | править код ]
В PHP замыкания — это
function add($x) return function ($y) use ($x) // return $x + $y; >; // > echo add(3)(5) . PHP_EOL; // Выведет: 8 $f = add(3); var_dump($f); // Выведет: object(Closure) echo $f(6) . PHP_EOL; // Выведет: 9
В PHP наследование переменных из родительской области видимости осуществляется с помощью конструкции use путем явного указания имен наследуемых переменных.
Другой пример с передачей замыкания в метод, где ожидается callable-параметр:
function power($arr, $exp) // переменная $func будет хранить ссылку на объект класса Closure, который описывает наше замыкание $func = function ($el) use ($exp) return $el ** $exp; >; return array_map($func, $arr); > $list = [1, 3, 4]; var_dump(power($list, 2)); // Выведет: array(3) int(1) [1]=>int(9) [2]=>int(16)> var_dump(power($list, 3)); // Выведет: array(3) int(1) [1]=>int(27) [2]=>int(64)>
См. также [ править | править код ]
- Лямбда-исчисление с типами
- Подстановка
- Модель акторов
Примечания [ править | править код ]
- Найти и оформить в виде сносок ссылки на независимые авторитетные источники, подтверждающие написанное.
Введение в замыкание. Closure
Замыкание обеспечивает доступ к переменным в своей лексической области; включая переменные родителей, которые были удалены из стека вызовов, путём определения, какие именно переменные понадобятся дочерним функциям, путём сохранения их в памяти.
Другими словами, замыкание даёт нам доступ к области видимости внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз, когда во время создания функции, внутри неё создаётся ещё одна функция.
Инкапсуляция позволяет нам скрывать/показывать свойства функций и объектов.
Замыкания обычно используются для обеспечения конфиденциальности данных объектов. Конфиденциальность данных — это важное свойство, которое помогает нам программировать интерфейс, а не реализацию. Дання концепция важна тем, что помогает нам создавать более надежное программное обеспечение.
В JavaScript, замыкания являются основным механизмом, обеспечивающим конфиденциальность данных. Когда мы используем замыкания для конфиденциальности данных, вложенные переменные находятся только в области действия, внутри содержащей (внешней) функции. Мы не можем получить данные из внешней области, кроме как через привилегированные функции. В JavaScript любая функция, определенная в области замыкания, является привилегированной.
Замыкания подобны объектам в том смысле, что они представляют собой механизм для хранения состояния:
Например, в приведенном ниже примере, мы не хотим показывать функцию launch для её вызова, а также не даём доступ к timeWithoutDesctruction :
const makeNuclearButton = () => // Переменные, опредёленные в области действия фабрики или конструктора // являются приватным для этой функции. // К ним нет доступа, если только мы не вернем их в качестве свойств объекта. let timeWithoutDesctruction = 0; const passTime = () => timeWithoutDesctruction++; // totalPeaceTime является привилегированной, так как она определена в области замыкания - // поэтому у неё есть доступ к timeWithoutDesctruction const totalPeaceTime = () => timeWithoutDesctruction; const launch = () => timeWithoutDesctruction = -1; return '' > setInterval(passTime, 1000); return totalPeaceTime>; > const button = makeNuclearButton(); button // button.totalPeaceTime(); // 1
Таким образом, мы скрываем данные объекта, которые не должны быть напрямую доступны. Вместо прямого доступа к данным нужно вызывать методы.
Эффективное использование памяти
Замыкания эффективны с точки зрения памяти. Используя замыкания, мы можем создать переменные, которые будут храниться в памяти и использоваться в будущем.
const closureTest1 = function() const bigArray = new Array(7000).fill('1'); console.log('created'); return function(index) return bigArray[index]; > > const closureTest1Fn = closureTest1(); closureTest1Fn(500); closureTest1Fn(300); closureTest1Fn(100); created // from console.log('created'); // returned "1"
// IIFE (Immediately Invoked Function Expression) const closureTest2 = (function() const bigArray = new Array(7000).fill('1'); console.log('created'); return function(index) return bigArray[index]; > >)(); closureTest2(500); closureTest2(300); closureTest2(100); created // from console.log('created'); // returned "1"
Мы вызываем функцию closureTest1Fn и closureTest2 3 раза, но console.log выводится только один раз. Это происходит потому, что мы, благодаря замыканию, сохраняем в памяти значения bigArray и console.log .
Типичный случай без замыкания приведет к неэффективному использованию памяти: переменная bigArray будет создаваться и сохраняться каждый раз.
function closureTest(index) const bigArray = new Array(7000).fill('1'); console.log('created'); return bigArray[index]; >; closureTest(500); closureTest(300); closureTest(100); created // from console.log('created'); created // from console.log('created'); created // from console.log('created'); // returned "1"
В функциональном программировании замыкания часто используются для частичного применения и каррирования.