Что такое замыкание в программировании
Перейти к содержимому

Что такое замыкание в программировании

Что такое замыкание в программировании

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

Для начала потребуется два термина: область видимости и стек. Если нужно вспомнить, раскрывайте:

Что такое область видимости

Область видимости определяет, к каким переменным функция, команда или другая переменная может получить доступ, а к каким нет. Проще говоря, что каждая функция «видит» — видит ли она те переменные и объекты, которые созданы за её пределами? Видит ли она то, что вложено в функции, которые запускаются внутри неё?

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

Что такое стек

Стек — это как список задач при выполнении программы. Сначала компьютер исполняет один код, внутри которого появляется какая-то функция — это как будто отдельная программа. Компьютер откладывает текущую программу, делает себе в стеке пометку «вернуться сюда, когда доделаю новую программу» и исполняет эту новую функцию. Исполнил — смотрит в стек, куда вернуться. Возвращается туда.

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

Что такое замыкание

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

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

Звучит сложно, объясним на примерах.

Что такое замыкание в программировании

Пример замыкания в коде

Сделаем функцию, которая вернёт нам фразу «Привет, …» вместе с именем, которое мы в него отправим. Но сделаем это через замыкание — чтобы посмотреть, как именно всё устроено.

Обратите внимание на две переменные в конце: 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 > 

Примечания [ править ]

  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"

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *