Как работают замыкания в JavaScript?

avatar
Zaheer Ahmed
21 сентября 2008 в 14:12
1521342
86
7627

Как бы вы объяснили замыкания JavaScript тому, кто знает концепции, из которых они состоят (например, функции, переменные и т. Д.), Но не понимает самих замыканий?

Я видел пример схемы, приведенный в Википедии, но, к сожалению, это не помогло.

Источник

Ответы (86)

avatar
Ben Aston
26 января 2021 в 13:08
7847

Замыкание - это пара:

  1. функция и
  2. Ссылка на внешнюю область действия этой функции (лексическое окружение)

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

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

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

В следующем коде inner образует замыкание с лексической средой контекста выполнения, созданной при вызове foo, закрытие над переменной secret:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

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

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

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

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

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

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

Использование закрытий

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

Переменные частного экземпляра

В следующем коде функция toString закрывает детали автомобиля.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Функциональное программирование

В следующем коде функция inner закрывается как для fn, так и для args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

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

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Событийно-ориентированное программирование

В следующем коде функция onClick закрывается над переменной BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

Модульность

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

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Примеры

Пример 1

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

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

Пример 2

В следующем коде три метода log, increment и update закрываются в одной и той же лексической среде.

И каждый раз, когда вызывается createObject, создается новый контекст выполнения (кадр стека) и создается совершенно новая переменная x, а также новый набор функций (log и т. Д.), Которые закрываются. эта новая переменная.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

Пример 3

Если вы используете переменные, объявленные с использованием var, будьте осторожны, вы понимаете, какую переменную вы закрываете. Поднимаются переменные, объявленные с использованием var. Это гораздо менее серьезная проблема в современном JavaScript из-за введения let и const.

В следующем коде каждый раз вокруг цикла создается новая функция inner, которая закрывается на i. Но поскольку var i поднимается вне цикла, все эти внутренние функции закрываются по одной и той же переменной, что означает, что окончательное значение i (3) печатается три раза.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Конечные точки:

  • Каждый раз, когда функция объявляется в JavaScript, создается закрытие.
  • Возврат function изнутри другой функции является классическим примером закрытия, поскольку состояние внутри внешней функции неявно доступно для возвращаемой внутренней функции даже после того, как внешняя функция завершила выполнение.
  • Когда вы используете eval() внутри функции, используется закрытие. Текст, который вы eval может ссылаться на локальные переменные функции, а в нестрогом режиме вы даже можете создавать новые локальные переменные, используя eval('var foo = …').
  • Когда вы используете new Function(…) (конструктор функций) внутри функции, он не закрывается по своей лексической среде: вместо этого он закрывается по глобальному контексту. Новая функция не может ссылаться на локальные переменные внешней функции.
  • Замыкание в JavaScript похоже на сохранение ссылки ( НЕ копии) на область видимости в точке объявления функции, которая, в свою очередь, сохраняет ссылку на ее внешнюю область видимости и т. Д., Все путь к глобальному объекту наверху цепочки областей видимости.
  • Замыкание создается при объявлении функции; это закрытие используется для настройки контекста выполнения при вызове функции.
  • Новый набор локальных переменных создается каждый раз при вызове функции.

Ссылки

avatar
Pao Im
4 ноября 2021 в 18:09
3

Замыкания в JavaScript связаны с концепцией областей действия.

До es6 не было области уровня блока, в JS есть только область уровня функции.

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

Посмотрите этот простой и интересный пример, как закрытие решает эту проблему в ES5

// let say we can only use a traditional for loop, not the forEach

for (var i = 0; i < 10; i++) {
    
    setTimeout(function() {
        console.log('without closure the visited index - '+ i)
    })
}

// this will print 10 times 'visited index - 10', which is not correct

/**
Expected output is 

visited index - 0
visited index - 1
.
.
.
visited index - 9

**/

// we can solve it by using closure concept 
   //by using an IIFE (Immediately Invoked Function Expression)


// --- updated code ---

for (var i = 0; i < 10; i++) {
    (function (i) {
      setTimeout(function() {
        console.log('with closure the visited index - '+ i)
      })
    })(i);
}

NB: эту проблему легко решить, используя es6 let вместо var, поскольку let создает лексическую область видимости.


Проще говоря, Closure в JS - это не что иное, как доступ к области действия функции.

avatar
GoodnessGoodness Chi
14 июня 2021 в 08:18
16

Может быть, вам стоит рассмотреть объектно-ориентированную структуру вместо внутренних функций. Например:

    var calculate = {
        number: 0,
        init: function (num) {
            this.number = num;
        },
        add: function (val) {
            this.number += val;
        },
        rem: function (val) {
            this.number -= val;
        }
    };

И прочтите результат из переменной calculate.number, которому все равно нужен "возврат".

//Addition
First think about scope which defines what variable you have to access to (In Javascript);

//there are two kinds of scope
Global Scope which include variable declared outside function or curly brace

let globalVariable = "foo";

Следует иметь в виду, что как только вы объявили глобальную переменную, вы можете использовать ее в любом месте вашего кода, даже в функции;

Локальная область, включающая переменные, которые можно использовать только в определенной части вашего кода:

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

function User(){
    let name = "foo";
    alert(name);
}
alert(name);//error

//Block scope is when you declare a variable within a block then you can  access that variable only within a block 
{
    let user = "foo";
    alert(user);
}
alert(user);
//Uncaught ReferenceError: user is not defined at.....

//A Closure

function User(fname){
    return function(lname){
        return fname + " " lname;
    }
}
let names = User("foo");
alert(names("bar"));

//When you create a function within a function you've created a closure, in our example above since the outer function is returned the inner function got access to outer function's scope
avatar
Shushanth Pallegar
9 ноября 2020 в 13:27
36

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

function getFullName(a, b) {
  return a + b;
}

function makeFullName(fn) {

  return function(firstName) {

    return function(secondName) {

      return fn(firstName, secondName);

    }
  }
}

makeFullName(getFullName)("Stack")("overflow"); // Stackoverflow
avatar
Konrad Rudolph
30 июня 2020 в 08:16
522

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

const makePlus = function(x) {
    return function(y) { return x + y; };
}

const plus5 = makePlus(5);
console.log(plus5(3));

Что бы произошло, если бы JavaScript не знал закрытие? Просто замените вызов в последней строке его телом метода (что в основном то, что делают вызовы функций), и вы получите:

console.log(x + 3);

А где определение x? Мы не определили это в текущем объеме. Единственное решение - позволить plus5 переносить свою область действия (или, скорее, область его родительского объекта). Таким образом, x четко определен и привязан к значению 5.

Shane
10 июля 2020 в 18:45
0

«они используются, чтобы заставить работать какое-то поведение, которое все интуитивно ожидает в любом случае». Оцените этот комментарий, поскольку отчасти это было то, с чем я боролся. Мне казалось, что я чего-то упускаю, но оказалось, что это не так!

avatar
floribon
20 июня 2020 в 09:12
107

Я не понимаю, почему ответы здесь такие сложные.

Вот закрытие:

var a = 42;

function b() { return a; }

Да. Вероятно, вы используете это много раз в день.


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

Теперь то, что позволяет сделать, может быть более впечатляющим, см. Другие ответы.

avatar
Chev
20 июня 2020 в 09:12
182

Я склонен лучше учиться, сравнивая ХОРОШО / ПЛОХО. Мне нравится видеть рабочий код, за которым следует нерабочий код, с которым кто-то может столкнуться. Я собрал jsFiddle, который сравнивает и пытается свести различия к простейшим объяснениям, которые я мог придумать.

Закрытия выполнены правильно:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • В приведенном выше коде createClosure(n) вызывается на каждой итерации цикла. Обратите внимание, что я назвал переменную n, чтобы подчеркнуть, что это новая переменная , созданная в новой области действия функции, и не та же переменная, что и index, привязанная к внешней области.

  • Это создает новую область, и n привязан к этой области; это означает, что у нас есть 10 отдельных областей видимости, по одной для каждой итерации.

  • createClosure(n) возвращает функцию, которая возвращает n в пределах этой области.

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

Закрытие выполнено неправильно:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • В приведенном выше коде цикл был перемещен внутри функции createClosureArray(), и теперь функция просто возвращает завершенный массив, что на первый взгляд кажется более интуитивным.

  • Что может быть неочевидным, так это то, что, поскольку createClosureArray() вызывается только один раз, только одна область видимости создается для этой функции вместо одной для каждой итерации цикла.

  • В этой функции определена переменная с именем index. Цикл запускается и добавляет в массив функции, возвращающие index. Обратите внимание, что index определен в функции createClosureArray, которая вызывается только один раз.

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

  • Все функции, добавленные в массив, возвращают ТАКУЮ переменную index из родительской области, в которой она была определена, вместо 10 разных из 10 разных областей, как в первом примере. Конечным результатом является то, что все 10 функций возвращают одну и ту же переменную из одной и той же области.

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

Результат

ЗАКРЫТИЕ ПРАВИЛЬНО
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

НЕПРАВИЛЬНОЕ ЗАКРЫТИЕ
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

avatar
Chris S
20 июня 2020 в 09:12
207

Можете ли вы объяснить закрытие 5-летнему ребенку? *

Я все еще думаю, что объяснение Google работает очень хорошо и кратко:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

Proof that this example creates a closure even if the inner function doesn't return

* Вопрос C #

avatar
jondavidjohn
20 июня 2020 в 09:12
606

Соломенный человек

Мне нужно знать, сколько раз была нажата кнопка, и делать что-то при каждом третьем нажатии ...

Достаточно очевидное решение

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

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

Рассмотрите этот вариант

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Обратите внимание на несколько моментов.

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

Простое однострочное закрытие

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

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

func();  // Alerts "val"
func.a;  // Undefined

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

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

Вот и все; теперь вы полностью инкапсулируете это поведение.

Полная запись в блоге (включая рекомендации jQuery)

avatar
soundyogi
20 июня 2020 в 09:12
10

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

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

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo
  // foo is free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure < >

avatar
Yevhen Tiurin
20 июня 2020 в 09:12
11

Замыкания простые

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

enter image description here

function getA() {
  var a = [];

  // this action happens later,
  // after the function returned
  // the `a` value
  setTimeout(function() {
    a.splice(0, 0, 1, 2, 3, 4, 5);
  });

  return a;
}

var a = getA();
out('What is `a` length?');
out('`a` length is ' + a.length);

setTimeout(function() {
  out('No wait...');
  out('`a` length is ' + a.length);
  out('OK :|')
});
<pre id="output"></pre>

<script>
  function out(k) {
    document.getElementById('output').innerHTML += '> ' + k + '\n';
  }
</script>
avatar
Nick Manning
20 июня 2020 в 09:12
4

Самый простой, самый короткий и самый легкий для понимания ответ:

Замыкание - это блок кода, в котором каждая строка может ссылаться на один и тот же набор переменных с одинаковыми именами переменных.

Если «это» означает нечто иное, чем где-либо еще, то вы знаете, что это два разных закрытия.

avatar
Ali
28 марта 2020 в 05:44
4072

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

Итак, всякий раз, когда вы видите ключевое слово function, код внутри этой функции имеет доступ к переменным, объявленным вне функции.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Это будет регистрировать 16, потому что функция bar закрывается над параметром x и переменной tmp, оба из которых существуют в лексической среде внешней функции foo.

.

Функция bar вместе с ее связью с лексическим окружением функции foo является закрытием.

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

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

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

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

Самый простой пример закрытия:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

При вызове функции JavaScript создается новый контекст выполнения ec. Вместе с аргументами функции и целевым объектом этот контекст выполнения также получает ссылку на лексическую среду вызывающего контекста выполнения, то есть на переменные, объявленные во внешней лексической среде (в приведенном выше примере, как a, так и b ) доступны по адресу ec.

Каждая функция создает замыкание, потому что каждая функция имеет ссылку на свое внешнее лексическое окружение.

Обратите внимание, что переменные сами по себе видны внутри замыкания, не копий.

avatar
Ben
17 февраля 2020 в 19:56
402

TLDR

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

Подробнее

В терминологии спецификации ECMAScript, можно сказать, что замыкание реализовано с помощью [[Environment]] ссылки каждого объекта-функции, которая указывает на лексическое окружение <995663> в пределах в котором определена функция.

Когда функция вызывается через внутренний метод [[Call]], [[Environment]] ссылка на объект-функцию внешней среды копируется31630 960666 во внешнюю ссылку31630 96066 > записи среды вновь созданного контекста выполнения (кадр стека).

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

function f() {}

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

function g() {
    function h() {}
}

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

В следующем примере функция j закрывается над лексическим окружением функции i, что означает, что переменная x видна изнутри функции j спустя много времени после того, как функция i завершила выполнение316306: <96016306 >

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

При замыкании переменные во внешней лексической среде сами доступны, не копий.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

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

Обратите внимание, что в попытке улучшить ясность и точность этот ответ был существенно изменен по сравнению с исходным.

avatar
M.A.K. Ripon
8 ноября 2018 в 09:38
20

Функция выполняется в области объекта / функции, в которой она определена. Указанная функция может обращаться к переменным, определенным в объекте / функции, где она была определена во время выполнения.

И воспринимайте это буквально .... как код написан: P

avatar
Max Tkachenko
10 октября 2018 в 18:38
392

Хорошо, шестилетний вентилятор закрывается. Хотите послушать самый простой пример закрытия?

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

Вот как я могу преобразовать мою историю самолета в код.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");
avatar
dlaliberte
10 октября 2018 в 17:50
784

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

На Детское развитие: от 5 до 7 лет там написано:

Ваш ребенок сможет следовать двухэтапным инструкциям. Например, если вы скажете своему ребенку: «Иди на кухню и принеси мне мешок для мусора», он запомнит это направление.

Мы можем использовать этот пример для объяснения замыканий следующим образом:

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

Мы можем закодировать это в JavaScript следующим образом:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Дополнительные моменты, объясняющие, почему замыкания так интересны:

  • Каждый раз, когда вызывается makeKitchen(), создается новое закрытие с собственным отдельным trashBags.
  • Переменная trashBags является локальной для внутренней части каждой кухни и недоступна снаружи, но внутренняя функция свойства getTrashBag имеет к ней доступ.
  • Каждый вызов функции создает замыкание, но не было бы необходимости сохранять замыкание, если только внутренняя функция, имеющая доступ к внутренней части замыкания, не может быть вызвана извне замыкания. Здесь это делается при возврате объекта с помощью функции getTrashBag.
avatar
Arvand
14 мая 2018 в 20:51
83

Автор Closures довольно хорошо объяснил замыкания, объясняя причину, по которой они нам нужны, а также объяснил LexicalEnvironment, который необходим для понимания замыканий.
Вот краткое содержание:

Что делать, если к переменной обращаются, но она не является локальной? Как здесь:

Enter image description here

В этом случае интерпретатор находит переменную в внешний LexicalEnvironment объект.

Процесс состоит из двух шагов:

  1. Во-первых, когда создается функция f, она не создается в пустом пространство. Есть текущий объект LexicalEnvironment. В случае выше, это окно (a не определено во время функции создание).

Enter image description here

Когда функция создается, она получает скрытое свойство с именем [[Scope]], которое ссылается на текущую LexicalEnvironment.

Enter image description here

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

Вложенные функции

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

Enter image description here

Итак, функция g имеет доступ к g, a и f.

Корпуса

Вложенная функция может продолжать работать после завершения внешней функции:

Enter image description here

Разметка лексических сред:

Enter image description here

Как мы видим, this.say - это свойство в объекте пользователя, поэтому оно продолжает существовать после того, как Пользователь завершил работу.

И если вы помните, когда this.say создается, она (как и каждая функция) получает внутреннюю ссылку this.say.[[Scope]] на текущую LexicalEnvironment. Таким образом, LexicalEnvironment текущего выполнения User остается в памяти. Все переменные пользователя также являются его свойствами, поэтому они также тщательно хранятся, а не выбрасываются, как обычно.

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

Подводя итог:

  1. Внутренняя функция сохраняет ссылку на внешнюю LexicalEnvironment.
  2. Внутренняя функция может получить доступ к переменным из нее в любое время, даже если внешняя функция завершена.
  3. Браузер сохраняет LexicalEnvironment и все его свойства (переменные) в памяти до тех пор, пока на него не будет ссылаться внутренняя функция.

Это называется закрытием.

avatar
Rafael Eyng
11 марта 2018 в 01:34
23

(я не беру в расчет 6-летнюю давность.)

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

var name = 'Rafael';

var sayName = function() {
  console.log(name);
};

Видите ли, sayName не имеет определения для переменной name, но он использует значение name, которое было определено вне sayName (в родительской области).

Допустим, вы передаете sayName в качестве параметра другой функции, которая вызовет sayName в качестве обратного вызова:

functionThatTakesACallback(sayName);

Обратите внимание:

  1. sayName будет вызываться изнутри functionThatTakesACallback (предположим, что, поскольку я не реализовал functionThatTakesACallback в этом примере).
  2. Когда вызывается sayName, он регистрирует значение переменной name.
  3. functionThatTakesACallback не определяет переменную name (ну, может, но это не имеет значения, поэтому предположим, что это не так).

Итак, sayName вызывается внутри functionThatTakesACallback и ссылается на переменную name, которая не определена внутри functionThatTakesACallback.

Что происходит потом? A ReferenceError: name is not defined?

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

Итак: даже несмотря на то, что name не входит в область, где будет вызвана функция sayName (внутри functionThatTakesACallback), sayName может получить доступ к значению для name, которое зафиксировано в замыкании, связанном с sayName.

-

Из книги Красноречивый JavaScript :

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

avatar
Alireza
13 ноября 2017 в 05:04
35

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

Таким образом, замыкание - это функция другой функции. Можно сказать как дочерняя функция.

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

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

Вы создаете замыкание, добавляя функцию внутри другой функции.

Кроме того, это очень полезный метод, который используется во многих известных фреймворках, включая Angular, Node.js и jQuery:

Замыкания широко используются в Node.js; они рабочие лошадки в Асинхронная неблокирующая архитектура Node.js. Закрытие также часто используется в jQuery и практически во всех частях JavaScript код, который вы читаете.

Но как замыкания выглядят в реальном коде? Взгляните на этот простой пример кода:

function showName(firstName, lastName) {
      var nameIntro = "Your name is ";
      // this inner function has access to the outer function's variables, including the parameter
      function makeFullName() {
          return nameIntro + firstName + " " + lastName;
      }
      return makeFullName();
  }

  console.log(showName("Michael", "Jackson")); // Your name is Michael Jackson

Кроме того, это классический способ закрытия в jQuery, который часто использовали все разработчики javascript и jQuery:

$(function() {
    var selections = [];
    $(".niners").click(function() { // this closure has access to the selections variable
        selections.push(this.prop("name")); // update the selections variable in the outer function's scope
    });
});

Но почему мы используем замыкания? когда мы используем это в реальном программировании? какова практическая польза укупорочных средств? ниже приведено хорошее объяснение и пример от MDN:

Практические затворы

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

Следовательно, вы можете использовать закрытие везде, где обычно использовать объект только с одним методом.

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

Например, предположим, что мы хотим добавить несколько кнопок на страницу, которая отрегулируйте размер текста. Один из способов сделать это - указать font-size основного элемента в пикселях, затем установите размер другие элементы на странице (например, заголовки) с использованием относительной em блок:

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

//javascript
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
/*css*/
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}
<!--html><!-->
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

Для дальнейшего изучения закрытий я рекомендую вам посетить эту страницу MDN: https://developer.mozilla.org/en/docs/Web/JavaScript/Closures

avatar
Jacob Swartwood
1 ноября 2017 в 11:40
2526

ПРЕДИСЛОВИЕ: этот ответ был написан, когда вопрос был:

Как сказал старый Альберт: «Если вы не можете объяснить это шестилетнему ребенку, вы действительно этого не понимаете». Ну, я попытался объяснить закрытие JS 27-летнему другу и полностью не удалось.

Кто-нибудь может считать, что мне 6 лет, и я странно интересуюсь этим предметом?

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


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

Давным-давно:

Была принцесса ...

function princess() {

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

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Но ей всегда приходилось возвращаться в свой скучный мир хлопот и взрослых.

    return {

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

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Но все, что они увидят, это маленькую девочку ...

var littleGirl = princess();

... рассказывать истории о магии и фэнтези.

littleGirl.story();

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

Но мы знаем настоящую правду; что маленькая девочка с принцессой внутри ...

... действительно принцесса с маленькой девочкой внутри.

Patrick M
28 февраля 2013 в 07:49
366

Мне правда нравится это объяснение. Для тех, кто читает и не следит за ней, можно провести аналогию: функция princess () - это сложная область видимости, содержащая частные данные. Вне функции личные данные не могут быть просмотрены или доступны. Принцесса хранит единорогов, драконов, приключения и т. Д. В своем воображении (личные данные), и взрослые не могут их увидеть сами. НО воображение принцессы уловлено в закрытии функции story(), которая является единственным интерфейсом, который экземпляр littleGirl предоставляет миру магии.

Hugolpz
2 сентября 2020 в 19:13
2

Неопределенные значения затрудняют понимание. Вот настоящая история jsfiddle.net/rjdx34k0/3

Tiago Martins Peres 李大仁
23 октября 2020 в 07:34
0

О, хорошо, я был так близок к тому, чтобы внести правку, чтобы удалить то, что я считал лишним пространством в начале. Хорошая работа, +1

Shivam
13 января 2021 в 05:15
0

И Прекрасный Принц может добавить к ее приключениям, может убить всех драконов, чтобы спасти ее от опасностей, как показано ниже: function princeCharming { adventures.push('Honeymoon Trip', 'Skydiving', 'Visiting Somalia'); const pickADragonToKill = dragons.pop(); }

avatar
Tero Tolonen
13 июля 2017 в 11:27
133

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

Секретами функций JavaScript являются частные переменные

var parent = function() {
 var name = "Mary"; // secret
}

Каждый раз, когда вы вызываете ее, создается локальная переменная name, которой присваивается имя Mary. И каждый раз при выходе из функции переменная теряется, а имя забывается.

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

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

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

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

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

Итак, чтобы жить, ребенок должен уйти, пока не стало слишком поздно

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

И теперь, хотя Мэри «больше не работает», память о ней не потеряна, и ее ребенок всегда будет помнить ее имя и другие секреты, которыми они поделились во время совместной жизни.

Итак, если вы назовете ребенка «Алиса», она ответит

child("Alice") => "My name is Alice, child of Mary"

Вот и все, что можно сказать.

avatar
Matt
23 мая 2017 в 12:10
219

Замыкания просты:

Следующий простой пример охватывает все основные моменты закрытия JavaScript. *

Вот фабрика, которая производит калькуляторы, которые умеют складывать и умножать:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Ключевой момент: Каждый вызов make_calculator создает новую локальную переменную n, которая по-прежнему может использоваться калькулятором add и multiply возвращает долгое время после <397396> функций. .

Если вы знакомы с кадрами стека, эти калькуляторы покажутся странными: как они могут продолжать обращаться к n после возврата make_calculator? Ответ состоит в том, чтобы представить, что JavaScript не использует «кадры стека», а вместо этого использует «кадры кучи», которые могут сохраняться после вызова функции, который их возвратил.

Внутренние функции, такие как add и multiply, которые обращаются к переменным, объявленным во внешней функции ** , называются закрытием <5>. <174256050>

Это почти все, что касается замыканий.



* Например, он охватывает все пункты статьи «Замыкания для чайников», приведенные в другом ответе, кроме примера 6, который просто показывает, что переменные можно использовать до их объявления. , факт, который стоит знать, но совершенно не связанный с закрытием. Он также охватывает все точки принятого ответа, за исключением точек (1), в которых функции копируют свои аргументы в локальные переменные (аргументы именованной функции), и (2), что при копировании чисел создается новое число, но при копировании ссылки на объект дает вам еще одну ссылку на тот же объект. Это тоже полезно знать, но опять же, совершенно не связанное с закрытием. Он также очень похож на пример в этом ответе, но немного короче и менее абстрактен. Он не охватывает суть этого ответа или этого комментария, который заключается в том, что JavaScript затрудняет включение текущего значения переменной цикла во внутреннюю функцию: "подключение "step может быть выполнен только с помощью вспомогательной функции, которая включает вашу внутреннюю функцию и вызывается на каждой итерации цикла. (Строго говоря, внутренняя функция обращается к копии переменной вспомогательной функции, а не к чему-либо подключенному.) Опять же, очень полезно при создании замыканий, но не является частью того, что такое замыкание или как оно работает. Существует дополнительная путаница из-за того, что замыкания работают по-разному в функциональных языках, таких как ML, где переменные привязаны к значениям, а не к пространству хранения, обеспечивая постоянный поток людей, которые понимают замыкания таким образом (а именно, способ "подключения"), который просто неверно для JavaScript, где переменные всегда привязаны к пространству памяти, а не к значениям.

** Любая внешняя функция, если несколько вложены, или даже в глобальном контексте, как ясно указывает этот ответ.

avatar
roland
23 мая 2017 в 10:31
22

Чем больше я думаю о закрытии, тем больше я рассматриваю его как двухэтапный процесс: init - action

init: pass first what's needed...
action: in order to achieve something for later execution.

Для 6-летнего ребенка я бы выделил практический аспект закрытия:

Daddy: Listen. Could you bring mum some milk (2).
Tom: No problem.
Daddy: Take a look at the map that Daddy has just made: mum is there and daddy is here.
Daddy: But get ready first. And bring the map with you (1), it may come in handy
Daddy: Then off you go (3). Ok?
Tom: A piece of cake!

Пример : Принесите немного молока маме (= действие). Сначала приготовьтесь и принесите карту (= init).

function getReady(map) {
    var cleverBoy = 'I examine the ' + map;
    return function(what, who) {
        return 'I bring ' + what + ' to ' + who + 'because + ' cleverBoy; //I can access the map
    }
}
var offYouGo = getReady('daddy-map');
offYouGo('milk', 'mum');

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

offYouGo('potatoes', 'great mum');

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

См. Еще один мой ответ, иллюстрирующий параллелизм между объектно-ориентированным взаимодействием и замыканиями:

Как «правильно» создать пользовательский объект в JavaScript?

avatar
Shivprasad Koirala
26 апреля 2017 в 01:21
21

Этот ответ представляет собой резюме этого видео на YouTube Замыкания Javascript. Итак, все кредиты этому видео.

Замыкания - это не что иное, как функции с отслеживанием состояния, которые поддерживают состояния своих частных переменных.

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

enter image description here

Но теперь бывают ситуации, когда мы хотим поддерживать это состояние функции, и именно здесь можно использовать закрытие Javascript. Замыкание - это функция внутри функции с обратным вызовом, как показано в приведенном ниже коде.

enter image description here

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

function Counter() {
           var counter = 0;

           var Increment = function () {
               counter++;
               alert(counter);
           }
           return {
               Increment
           }
       }

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

var x = Counter(); // get the reference of the closure
x.Increment(); // Displays 1
x.Increment(); // Display 2 ( Maintains the private variables)

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

Итак, все, что вы хотите инкапсулировать, вы можете сделать частным, а вещи, которые будут открыты для публики, должны быть помещены в оператор return. Кроме того, эти компоненты являются автономными изолированными объектами, поэтому они не загрязняют глобальные переменные.

Объект, который следует принципам ООП, является самодостаточным, следует абстракции, следует инкапсуляции и т.д. Без закрытий в Javascript это сложно реализовать.

enter image description here

avatar
Alireza
10 апреля 2017 в 12:50
7

Начнем отсюда, как определено в MDN: Замыкания - это функции, которые ссылаются на независимые (свободные) переменные (переменные, которые используются локально, но определены во внешней области видимости) . Другими словами, эти функции «запоминают» среду, в которой они были созданы.

Лексическая область видимости
Обратите внимание на следующее:

function init() {
  var name = 'Mozilla'; // name is a local variable created by init
  function displayName() { // displayName() is the inner function, a closure
    alert(name); // use variable declared in the parent function    
  }
  displayName();    
}
init();

init () создает локальную переменную с именем name и функцию с именем displayName (). Функция displayName () - это внутренняя функция, которая определяется внутри init () и доступна только в теле функции init (). Функция displayName () не имеет собственных локальных переменных. Однако, поскольку внутренние функции имеют доступ к переменным внешних функций, displayName () может обращаться к имени переменной, объявленной в родительской функции, init ().

function init() {
    var name = "Mozilla"; // name is a local variable created by init
    function displayName() { // displayName() is the inner function, a closure
        alert (name); // displayName() uses variable declared in the parent function    
    }
    displayName();    
}
init();

Запустите код и обратите внимание, что оператор alert () в функции displayName () успешно отображает значение переменной name, которая объявлена ​​в ее родительской функции. Это пример лексической области видимости, который описывает, как синтаксический анализатор разрешает имена переменных, когда функции вложены. Слово «лексический» относится к тому факту, что лексическая область видимости использует место, где объявлена ​​переменная в исходном коде, чтобы определить, где эта переменная доступна. Вложенные функции имеют доступ к переменным, объявленным во внешней области видимости.

Закрытие
Теперь рассмотрим следующий пример:

function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Выполнение этого кода имеет тот же эффект, что и предыдущий пример функции init () выше: на этот раз строка «Mozilla» будет отображаться в окне предупреждения JavaScript. Отличается - и интересно - то, что внутренняя функция displayName () возвращается из внешней функции перед выполнением.

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

Причина в том, что функции в JavaScript закрывают формы. Замыкание - это комбинация функции и лексического окружения, в котором эта функция была объявлена. Эта среда состоит из любых локальных переменных, которые были в области видимости во время создания замыкания. В этом случае myFunc - это ссылка на экземпляр функции displayName, созданный при запуске makeFunc. Экземпляр displayName поддерживает ссылку на свою лексическую среду, в которой существует имя переменной. По этой причине, когда вызывается myFunc, имя переменной остается доступным для использования, а для предупреждения передается "Mozilla".

Вот немного более интересный пример - функция makeAdder:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

В этом примере мы определили функцию makeAdder (x), которая принимает единственный аргумент x и возвращает новую функцию. Возвращаемая функция принимает единственный аргумент y и возвращает сумму x и y.

По сути, makeAdder - это фабрика функций - она ​​создает функции, которые могут добавлять определенное значение к своим аргументам. В приведенном выше примере мы используем нашу фабрику функций для создания двух новых функций: одна добавляет 5 к своему аргументу, а вторая - 10.

add5 и add10 являются замыканиями. Они используют одно и то же определение тела функции, но хранят разные лексические среды. В лексической среде add5 x равно 5, а в лексической среде add10 x равно 10.

Практические крышки

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

Следовательно, вы можете использовать замыкание везде, где вы обычно можете использовать объект только с одним методом.

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

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

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Наши интерактивные кнопки размера текста могут изменять свойство font-size основного элемента, и настройки будут улавливаться другими элементами на странице благодаря относительным единицам. Вот код JavaScript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14 и size16 теперь являются функциями, которые изменяют размер основного текста до 12, 14 и 16 пикселей соответственно. Мы можем прикрепить их к кнопкам (в данном случае ссылкам) следующим образом:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>


function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

, чтобы узнать больше о закрытии, посетите ссылку в MDN

avatar
zak.http
22 марта 2017 в 09:28
4

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

function multiplier(n) {
    function multiply(x) {
          return n*x;
    }
    return mutliply;
}

var 10xmultiplier = multiplier(10);
var x = 10xmultiplier(5); // x= 50

мы видим, что даже после того, как умножитель завершил выполнение, внутренняя функция multiply по-прежнему получает доступ к значению x, которое в этом примере равно 10.

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

Мы можем достичь этого, потому что Javascript (в дополнение к прототипу ООП) позволяет программировать в функциональном стиле, когда функции более высокого порядка могут принимать другие функции в качестве аргументов (функции первого класса). функциональное программирование в википедии

Я настоятельно рекомендую вам прочитать эту книгу Кайла Симпсона: 2 одна часть серии книг посвящена замыканиям и называется областью действия и замыканиями. вы не знаете js: бесплатное чтение на github

avatar
20 января 2017 в 22:14
7

Моя точка зрения на закрытие:

Закрытие можно сравнить с книгой с закладкой на книжной полке.

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

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

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

Пример кода:

function book() {
   var pages = [....]; //array of pages in your book
   var bookMarkedPage = 20; //bookmarked page number
   function getPage(){
       return pages[bookMarkedPage];
   }
   return getPage;
}

var myBook = book(),
    myPage = myBook.getPage();

Когда вы запускаете функцию book(), вы выделяете в стеке память для выполнения функции. Но поскольку она возвращает функцию, память не может быть освобождена, поскольку внутренняя функция имеет доступ к переменным из контекст вне его, в данном случае 'pages' и 'bookMarkedPage'.

Таким образом, эффективный вызов book() возвращает ссылку на закрытие, то есть не только функцию, но и ссылку на книгу и ее контекст, то есть ссылку на функцию getPage , состояние страницы и bookMarkedPage переменные.

Некоторые моменты, которые следует учитывать:

Точка 1: Книжная полка, как и стек функций, имеет ограниченное пространство, поэтому используйте ее с умом.

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

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

Robse
1 апреля 2021 в 08:26
0

Очень умная аналогия!

avatar
Alexis
18 января 2017 в 13:06
8

Замыкание не сложно понять. Это зависит только от точки зрения.

Лично мне нравится использовать их в повседневной жизни.

function createCar()
{
    var rawMaterial = [/* lots of object */];
    function transformation(rawMaterials)
    {
       /* lots of changement here */
       return transformedMaterial;
    }
    var transformedMaterial = transformation(rawMaterial);
    function assemblage(transformedMaterial)
    {
        /*Assemblage of parts*/
        return car;
    }
    return assemblage(transformedMaterial);
}

Нам нужно выполнить только определенные шаги в определенных случаях. Что касается трансформации материалов, это полезно только тогда, когда у вас есть детали.

avatar
Abrar Jahin
17 октября 2016 в 10:46
26

Замыкания позволяют программистам JavaScript писать лучший код. Креативный, выразительный и лаконичный. Мы часто используем замыкания в JavaScript, и, независимо от нашего опыта работы с JavaScript, мы, несомненно, сталкиваемся с ними снова и снова. Замыкания могут показаться сложными, но, надеюсь, после того, как вы прочтете это, замыкания станут более понятными и, следовательно, более привлекательными для ваших повседневных задач программирования на JavaScript.

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

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

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

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

Вы создаете замыкание, добавляя функцию внутри другой функции.

Базовый пример замыканий в JavaScript:

function showName (firstName, lastName) {

  var nameIntro = "Your name is ";
  // this inner function has access to the outer function's variables, including the parameter
  ​function makeFullName () {
            
​    return nameIntro + firstName + " " + lastName;
        
  }
​
​  return makeFullName ();

}

​
showName ("Michael", "Jackson"); // Your name is Michael Jackson


Замыкания широко используются в Node.js; они - рабочие лошадки в асинхронной неблокирующей архитектуре Node.js. Замыкания также часто используются в jQuery и практически в каждом фрагменте кода JavaScript, который вы читаете.

Классический пример закрытия jQuery:

$(function() {
​
​  var selections = []; 
  $(".niners").click(function() { // this closure has access to the selections variable​
    selections.push (this.prop("name")); // update the selections variable in the outer function's scope​
  });
​});

Правила и побочные эффекты закрытия

1. Замыкания имеют доступ к переменной внешней функции даже после возврата из внешней функции:

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

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
    // this inner function has access to the outer function's variables, including the parameter​
   function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}
​
​var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.​
​
​// The closure (lastName) is called here after the outer function has returned above​
​// Yet, the closure still has access to the outer function's variables and parameter​
mjName ("Jackson"); // This celebrity is Michael Jackson


2. Замыкания хранят ссылки на переменные внешней функции:

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

function celebrityID () {
    var celebrityID = 999;
    // We are returning an object with some inner functions​
    // All the inner functions have access to the outer function's variables​
    return {
        getID: function ()  {
            // This inner function will return the UPDATED celebrityID variable​
            // It will return the current value of celebrityID, even after the changeTheID function changes it​
          return celebrityID;
        },
        setID: function (theNewID)  {
            // This inner function will change the outer function's variable anytime​
            celebrityID = theNewID;
        }
    }
​
}
​
​var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.​
mjID.getID(); // 999​
mjID.setID(567); // Changes the outer function's variable​
mjID.getID(); // 567: It returns the updated celebrityId variable


3. Ушло время закрытия

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

// This example is explained in detail below (just after this code box).​
​function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
      theCelebrities[i]["id"] = function ()  {
        return uniqueID + i;
      }
    }
    
    return theCelebrities;
}
​
​var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
​
​var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
​
​var stalloneID = createIdForActionCelebs [0];

    console.log(stalloneID.id()); // 103


Больше можно найти здесь-

  1. http://javascript.info/tutorial/closures

  2. http://www.javascriptkit.com/javatutors/closures.shtml

avatar
20 сентября 2016 в 08:22
12

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

Функции первого класса

В языках программирования функции считаются первоклассными гражданами, если ими можно манипулировать, как любым другим типом данных. Например, первоклассные функции могут быть созданы во время выполнения и назначены переменным. Они также могут передаваться и возвращаться другими функциями. Помимо соответствия ранее упомянутым критериям, функции JavaScript также имеют свои собственные свойства и методы. В следующем примере показаны некоторые возможности первоклассных функций. В этом примере две функции создаются и назначаются переменным «foo» и «bar». Функция, хранящаяся в «foo», отображает диалоговое окно, а «bar» просто возвращает любой переданный ей аргумент. Последняя строка примера выполняет несколько функций. Сначала вызывается функция, хранящаяся в bar, с аргументом foo. «Bar» затем возвращает ссылку на функцию «foo». Наконец, вызывается возвращенная ссылка «foo», вызывая «Hello World!» для отображения.

var foo = function() {
  alert("Hello World!");
};

var bar = function(arg) {
  return arg;
};

bar(foo)();

Внутренние функции

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

function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3

Одной из важных характеристик внутренних функций является то, что они имеют неявный доступ к области действия внешней функции. Это означает, что внутренняя функция может использовать переменные, аргументы и т. Д. Внешней функции. В предыдущем примере аргументы « значение1 » и « значение2 » аргументы add () были переданы в doAdd () как операнд «. > »и аргументы« операнд2 ». Однако в этом нет необходимости, поскольку doAdd () имеет прямой доступ к « значение1 » и « значение2 ». Предыдущий пример был переписан ниже, чтобы показать, как doAdd () может использовать « значение1 » и « значение2 ».

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3

Создание замыканий

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

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3

В этом примере следует отметить несколько моментов.

Функция add () возвращает свою внутреннюю функцию doAdd (). Замыкание создается путем возврата ссылки на внутреннюю функцию. «Value1» - это локальная переменная add () и нелокальная переменная doAdd (). Нелокальные переменные относятся к переменным, которые не находятся ни в локальной, ни в глобальной области видимости. «Value2» - это локальная переменная doAdd (). Когда вызывается add (1), закрытие создается и сохраняется в «приращении». В ссылочной среде замыкания «значение1» привязано к значению «один». Связанные переменные также называются замкнутыми. Отсюда и название закрытия. Когда вызывается инкремент (2), происходит закрытие. Это означает, что вызывается doAdd () с переменной «value1», содержащей значение one. Замыкание можно рассматривать как создание следующей функции.

function increment(value2) {
  return 1 + value2;
}

Когда использовать замыкания

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

Работа с таймерами

Замыкания полезны при использовании вместе с функциями setTimeout () и setInterval () . Чтобы быть более конкретным, замыкания позволяют передавать аргументы функциям обратного вызова setTimeout () и setInterval () . Например, следующий код печатает строку «какое-то сообщение» один раз в секунду, вызывая showMessage () .

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      window.setInterval(showMessage, 1000, "some message<br />");
    });

    function showMessage(message) {
      document.getElementById("message").innerHTML += message;
    }
  </script>
</head>
<body>
  <span id="message"></span>
</body>
</html>

К сожалению, Internet Explorer не поддерживает передачу аргументов обратного вызова через setInterval (). Вместо того, чтобы отображать «какое-то сообщение», Internet Explorer отображает «undefined» (поскольку на самом деле в showMessage () не передается никакого значения). Чтобы обойти эту проблему, можно создать замыкание, которое привязывает аргумент «сообщение» к желаемому значению. Затем закрытие можно использовать как функцию обратного вызова для setInterval (). Чтобы проиллюстрировать эту концепцию, код JavaScript из предыдущего примера был переписан ниже, чтобы использовать закрытие.

window.addEventListener("load", function() {
  var showMessage = getClosure("some message<br />");

  window.setInterval(showMessage, 1000);
});

function getClosure(message) {
  function showMessage() {
    document.getElementById("message").innerHTML += message;
  }

  return showMessage;
}

Эмуляция личных данных

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

В следующем примере показан конструктор для простого класса Person. При создании каждого человека ему дается имя с помощью аргумента « name ». Внутри Person хранит свое имя в переменной « _name ». В соответствии с хорошими практиками объектно-ориентированного программирования для получения имени также предоставляется метод getName () .

function Person(name) {
  this._name = name;

  this.getName = function() {
    return this._name;
  };
}

Есть еще одна серьезная проблема с классом Person. Поскольку JavaScript не поддерживает личные данные, ничто не мешает кому-то другому прийти и изменить имя. Например, следующий код создает человека по имени Колин, а затем меняет его имя на Том.

var person = new Person("Colin");

person._name = "Tom";
// person.getName() now returns "Tom"

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

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

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

var person = new Person("Colin");

person._name = "Tom";
// person._name is "Tom" but person.getName() returns "Colin"

Когда не использовать закрытия

Важно понимать, как работают замыкания и когда их использовать. Не менее важно понимать, когда это не тот инструмент для работы. Чрезмерное использование закрытий может привести к выполнению скриптов. медленно и потребляют ненужную память. И потому что закрытие так просты в создании, ими можно злоупотреблять, даже не зная Это. В этом разделе рассматриваются несколько сценариев, в которых следует выполнять закрытие. используется с осторожностью.

В циклах

Создание замыканий внутри циклов может привести к ошибочным результатам. Пример этого показан ниже. В этом примере созданы три кнопки. При нажатии кнопки «button1» должно отображаться предупреждение с надписью «Нажата кнопка 1». Аналогичные сообщения должны отображаться для «button2» и «button3». Однако, когда этот код запускается, все кнопки показывают «Нажата кнопка 4». Это связано с тем, что к моменту нажатия одной из кнопок цикл завершается, и переменная цикла достигает своего окончательного значения, равного четырем.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      for (var i = 1; i < 4; i++) {
        var button = document.getElementById("button" + i);

        button.addEventListener("click", function() {
          alert("Clicked button " + i);
        });
      }
    });
  </script>
</head>
<body>
  <input type="button" id="button1" value="One" />
  <input type="button" id="button2" value="Two" />
  <input type="button" id="button3" value="Three" />
</body>
</html>

Чтобы решить эту проблему, замыкание должно быть отделено от фактической переменной цикла. Это можно сделать, вызвав новую функцию, которая, в свою очередь, создаст новую среду ссылок. В следующем примере показано, как это делается. Переменная цикла передается в функцию getHandler (). getHandler () затем возвращает закрытие, не зависящее от исходного цикла for.

function getHandler(i) {
  return function handler() {
    alert("Clicked button " + i);
  };
}
window.addEventListener("load", function() {
  for (var i = 1; i < 4; i++) {
    var button = document.getElementById("button" + i);
    button.addEventListener("click", getHandler(i));
  }
});

Ненужное использование в конструкторах

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

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };

  this.sayHello = function() {
    alert("Hello!");
  };
}

Каждый раз, когда создается экземпляр Person, время тратится на создание sayHello () метод. Если создается много объектов Person, это становится пустая трата времени. Лучшим подходом было бы добавить sayHello () к Прототип человека. Добавляя к прототипу, все объекты Person могут использовать тот же метод. Это экономит время в конструкторе, так как не необходимость создания закрытия для каждого экземпляра. Предыдущий пример переписано ниже с посторонним замыканием перенесено в прототип.

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Person.prototype.sayHello = function() {
  alert("Hello!");
};

Что нужно помнить

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

Ссылка

avatar
virus
31 июля 2016 в 18:39
20

Изображение версии для этого ответа: [Решено]

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

Пример 1:

enter image description here

Пример 2:

enter image description here

Пример 3: enter image description here

avatar
Dinesh Kanivu
13 июля 2016 в 04:20
42

Я верю в более короткие объяснения, поэтому см. Изображение ниже.

Enter image description here

function f1() ..> Светло-красный ящик

function f2() ..> Красная маленькая коробочка

Здесь у нас есть две функции: f1() и f2(). f2 () является внутренним по отношению к f1 (). f1 () имеет переменную var x = 10.

При вызове функции f1(), f2() может получить доступ к значению var x = 10.

Вот код:

function f1() {
    var x=10;

    function f2() {
        console.log(x)
    }

    return f2

}
f1()

f1() вызывая здесь:

Enter image description here

avatar
John Pick
8 июня 2016 в 22:16
77

Функции JavaScript могут обращаться к своим:

  1. Аргументы
  2. Локальные переменные (то есть их локальные переменные и локальные функции)
  3. Среда, в которую входят:
    • глобальные объекты, включая DOM
    • что-нибудь во внешних функциях

Если функция обращается к своему окружению, то функция является закрытием.

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

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

Представьте, что события кнопок «Голосование за переполнение стека» и «Голосование за отклонение» реализованы как закрытие, voteUp_click и voteDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки голосования за вопрос в StackOverflow, а не набор кнопок ответа на голосование.)

Когда пользователь нажимает кнопку VoteUp, функция voteUp_click проверяет, является ли isVotedDown == true, чтобы определить, голосовать ли за или просто отменить голосование против. Функция voteUp_click является закрытием, потому что она обращается к своей среде.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

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

avatar
grateful
8 июня 2016 в 22:11
59

Как отец 6-летнего ребенка, который в настоящее время преподает детям младшего возраста (и относительный новичок в программировании без формального образования, поэтому потребуются исправления), я думаю, что урок лучше всего будет усвоен через практическую игру. Если 6-летний ребенок готов понять, что такое закрытие, значит, он достаточно взрослый, чтобы попробовать себя. Я предлагаю вставить код в jsfiddle.net, немного пояснить и оставить их в покое, чтобы сочинить уникальную песню. Приведенный ниже пояснительный текст, вероятно, больше подходит для 10-летнего ребенка.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

ИНСТРУКЦИЯ

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

КОД: Все вышеперечисленное называется кодом . Он написан на JavaScript.

JAVASCRIPT: JavaScript - это язык. Как английский, французский или китайский языки. Есть много языков, которые понимают компьютеры и другие электронные процессоры. Чтобы компьютер мог понять JavaScript, ему нужен интерпретатор. Представьте, что к вашему классу в школе приходит учитель, который говорит только по-русски. Когда учитель говорит «все садятся», класс не понимает. Но, к счастью, в вашем классе есть русский ученик, который всем говорит, что это означает «все садитесь» - вы все так и делаете. Класс похож на компьютер, а русский ученик - переводчик. Для JavaScript наиболее распространенный интерпретатор называется браузером.

БРАУЗЕР: когда вы подключаетесь к Интернету на компьютере, планшете или телефоне для посещения веб-сайта, вы используете браузер. Возможно, вы знаете, что это Internet Explorer, Chrome, Firefox и Safari. Браузер понимает JavaScript и сообщает компьютеру, что ему нужно делать. Инструкции JavaScript называются функциями.

ФУНКЦИЯ: Функция в JavaScript похожа на фабрику. Это может быть небольшая фабрика с одной машиной внутри. Или он может содержать множество других маленьких фабрик, на каждой из которых множество машин, выполняющих разные работы. На реальной фабрике по производству одежды у вас могут быть стопки ткани и мотки ниток, а выходить футболки и джинсы. Наша фабрика JavaScript обрабатывает только данные, она не может шить, сверлить отверстие или плавить металл. В нашем JavaScript заводские данные входят, а данные выходят.

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

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

Функция обычно имеет имя, круглые скобки и фигурные скобки. Как это:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Обратите внимание, что /*...*/ и // останавливают чтение кода браузером.

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

Скобки: «Скобки» или () - это почтовый ящик на двери фабрики функций JavaScript или почтовый ящик на улице для отправки пакетов информации на фабрику. Иногда почтовый ящик может быть помечен как , например, cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), и в этом случае вы знаете, какие данные вы должны ему передать.

ОГРАНИЧЕНИЯ: «Подтяжки», которые выглядят так {}, представляют собой тонированные стекла нашей фабрики. Изнутри фабрики видно снаружи, но снаружи ничего не видно.

ПРИМЕР ДЛИННОГО КОДА ВЫШЕ

Наш код начинается со слова function , поэтому мы знаем, что это один! Затем имя функции sing - это мое собственное описание того, о чем функция. Затем круглые скобки () . Круглые скобки всегда используются для функции. Иногда они пустые, а иногда в них что-то есть. В этом есть слово: (person). После этого идет такая скобка {. Это отмечает начало функции sing () . У него есть партнер, который отмечает конец sing () вот так }

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

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

Теперь, после функции sing () , ближе к концу кода находится строка

var person="an old lady";

ПЕРЕМЕННАЯ: буквы var обозначают «переменную». Переменная подобна конверту. На внешней стороне конверта есть пометка «человек». Внутри он содержит листок бумаги с информацией, которая нужна нашей функции, несколько букв и пробелов, соединенных вместе, как кусок строки (она называется строкой), которые образуют фразу, читающую «старушка». Наш конверт может содержать другие типы вещей, такие как числа (называемые целыми числами), инструкции (называемые функциями), списки (называемые массивами ). Поскольку эта переменная записана за пределами фигурных скобок {}, и поскольку вы можете видеть сквозь тонированные окна, когда находитесь внутри фигурных скобок, эту переменную можно увидеть из любого места в коде. Мы называем это «глобальной переменной».

ГЛОБАЛЬНАЯ ПЕРЕМЕННАЯ: человек - это глобальная переменная, что означает, что если вы измените ее значение с «старушка» на «молодой человек», человек останется молодой человек, пока вы не решите изменить его снова и что любая другая функция в коде увидит, что это молодой человек. Нажмите кнопку F12 или просмотрите настройки параметров, чтобы открыть консоль разработчика браузера, и введите «человек», чтобы узнать, что это за значение. Введите person="a young man", чтобы изменить его, а затем снова введите "person", чтобы увидеть, что оно изменилось.

После этого у нас есть строка

sing(person);

Эта строка вызывает функцию, как если бы она вызывала собаку

«Давай пой , приходи и возьми человека

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

Функции определяют действия - основная функция - пение. Он содержит переменную с именем firstPart , которая применяется к пению о человеке, которое применяется к каждому из стихов песни: «Был» + человек + «который проглотил». Если вы наберете firstPart в консоли, вы не получите ответа, потому что переменная заблокирована в функции - браузер не видит внутри тонированных окон фигурных скобок.

ЗАКРЫТИЕ: Замыкания - это более мелкие функции, которые находятся внутри большой функции sing () . Маленькие фабрики внутри большой фабрики. У каждого из них есть свои фигурные скобки, что означает, что переменные внутри них не видны снаружи. Поэтому имена переменных ( существо и результат ) могут повторяться в замыканиях, но с другими значениями. Если вы введете эти имена переменных в окне консоли, вы не получите их значения, потому что они скрыты двумя слоями тонированных окон.

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

После закрытия идут строки

fly();
spider();
bird();
cat();

Функция sing () будет вызывать каждую из этих функций в том порядке, в котором они указаны. Тогда работа функции sing () будет завершена.

avatar
Ron Deijkers
8 июня 2016 в 22:11
13

Пиноккио: закрытие в 1883 году (более века до появления JavaScript)

Думаю, лучше всего это можно объяснить шестилетнему мальчику с помощью приятного приключения ... Часть Приключений Пиноккио, где Пиноккио проглатывает огромная морская рыба ...

var tellStoryOfPinocchio = function(original) {

  // Prepare for exciting things to happen
  var pinocchioFindsMisterGeppetto;
  var happyEnding;

  // The story starts where Pinocchio searches for his 'father'
  var pinocchio = {
    name: 'Pinocchio',
    location: 'in the sea',
    noseLength: 2
  };

  // Is it a dog... is it a fish...
  // The dogfish appears, however there is no such concept as the belly
  // of the monster, there is just a monster...
  var terribleDogfish = {
    swallowWhole: function(snack) {
      // The swallowing of Pinocchio introduces a new environment (for the
      // things happening inside it)...
      // The BELLY closure... with all of its guts and attributes
      var mysteriousLightLocation = 'at Gepetto\'s ship';

      // Yes: in my version of the story the monsters mouth is directly
      // connected to its belly... This might explain the low ratings
      // I had for biology...
      var mouthLocation = 'in the monsters mouth and then outside';

      var puppet = snack;


      puppet.location = 'inside the belly';
      alert(snack.name + ' is swallowed by the terrible dogfish...');

      // Being inside the belly, Pinocchio can now experience new adventures inside it
      pinocchioFindsMisterGeppetto = function() {
        // The event of Pinocchio finding Mister Geppetto happens inside the
        // belly and so it makes sence that it refers to the things inside
        // the belly (closure) like the mysterious light and of course the
        // hero Pinocchio himself!
        alert(puppet.name + ' sees a mysterious light (also in the belly of the dogfish) in the distance and swims to it to find Mister Geppetto! He survived on ship supplies for two years after being swallowed himself. ');
        puppet.location = mysteriousLightLocation;

        alert(puppet.name + ' tells Mister Geppetto he missed him every single day! ');
        puppet.noseLength++;
      }

      happyEnding = function() {
        // The escape of Pinocchio and Mister Geppetto happens inside the belly:
        // it refers to Pinocchio and the mouth of the beast.
        alert('After finding Mister Gepetto, ' + puppet.name + ' and Mister Gepetto travel to the mouth of the monster.');
        alert('The monster sleeps with its mouth open above the surface of the water. They escape through its mouth. ');
        puppet.location = mouthLocation;
        if (original) {
          alert(puppet.name + ' is eventually hanged for his innumerable faults. ');
        } else {
          alert(puppet.name + ' is eventually turned into a real boy and they all lived happily ever after...');
        }
      }
    }
  }

  alert('Once upon a time...');
  alert('Fast forward to the moment that Pinocchio is searching for his \'father\'...');
  alert('Pinocchio is ' + pinocchio.location + '.');
  terribleDogfish.swallowWhole(pinocchio);
  alert('Pinocchio is ' + pinocchio.location + '.');
  pinocchioFindsMisterGeppetto();
  alert('Pinocchio is ' + pinocchio.location + '.');
  happyEnding();
  alert('Pinocchio is ' + pinocchio.location + '.');

  if (pinocchio.noseLength > 2)
    console.log('Hmmm... apparently a little white lie was told. ');
}

tellStoryOfPinocchio(false);

 
avatar
Premraj
8 июня 2016 в 22:10
13

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

var add = (function() {
  var counter = 0;
  return function() {
    return counter += 1;
  }
})();

add();
add();
add();
// The counter is now 3

Пример объяснения:

  • Переменной add назначается возвращаемое значение функции с автоматическим запуском.
  • Самозапускающаяся функция запускается только один раз. Он устанавливает счетчик на ноль (0) и возвращает выражение функции.
  • Таким образом, add становится функцией. "Замечательно" то, что он может получить доступ к счетчику в родительской области.
  • Это называется закрытием JavaScript. Это позволяет функции иметь "частные" переменные.
  • Счетчик защищен областью действия анонимной функции и может быть изменен только с помощью функции сложения.

Источник

avatar
Gerardo Lima
9 мая 2016 в 11:32
87

Я знаю, что уже существует множество решений, но полагаю, что этот небольшой и простой скрипт может быть полезен для демонстрации концепции:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined
avatar
Nikhil Ranjan
6 мая 2016 в 10:34
21

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

Enter image description here

JavaScript выполняет код в два этапа:

  • Этап компиляции // JavaScript не является чистым интерпретируемым языком
  • Этап выполнения

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

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

Аргументы также сохраняются в этой локальной области для f. Всякий раз, когда создается локальный контекст выполнения или область видимости, она содержит указатель ссылки на свою родительскую область. Весь доступ к переменной следует этой цепочке лексической области видимости, чтобы найти ее значение. Если переменная не найдена в локальной области видимости, она следует по цепочке и находит ее в своей родительской области. По этой же причине локальная переменная переопределяет переменные в родительской области. Родительская область называется «Закрытием» для локальной области или функции.

Здесь при настройке области g он получил лексический указатель на родительскую область f. Область действия f - это закрытие для g. В JavaScript, если есть какая-то ссылка на функции, объекты или области, если вы можете каким-то образом до них добраться, сборщик мусора не будет. Итак, когда myG запущен, у него есть указатель на область f, которая является его закрытием. Эта область памяти не будет собираться сборщиком мусора, даже если f вернулся. Это закрытие для среды выполнения.

ТАК ЧТО ТАКОЕ ЗАКРЫТИЕ?

  • Это неявная постоянная связь между функцией и ее цепочкой областей видимости ...
  • Скрытая ссылка на определение функции (лямбда) [[scope]].
  • Содержит цепочку областей видимости (предотвращая сборку мусора).
  • Он используется и копируется как «ссылка на внешнюю среду» при каждом запуске функции.

НЕЯТНОЕ ЗАКРЫТИЕ

var data = "My Data!";
setTimeout(function() {
  console.log(data); // Prints "My Data!"
}, 3000);

ЯВНОЕ ЗАКРЫТИЕ

function makeAdder(n) {
  var inc = n;
  var sum = 0;
  return function add() {
    sum = sum + inc;
    return sum;
  };
}

var adder3 = makeAdder(3);

Очень интересный доклад о замыканиях и многом другом: Ариндам Пол - внутреннее устройство виртуальной машины JavaScript, EventLoop, Async и ScopeChains .

.

avatar
ejectamenta
6 мая 2016 в 10:22
8

Для шестилетнего ребенка ...

Вы знаете, что такое объекты?

Объекты - это вещи, у которых есть свойства и которые делают что-то.

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

Объекты очень полезны и содержат все в порядке и порядке. Разные объекты могут выполнять разные задачи, а совместная работа объектов может выполнять сложные задачи.

К счастью, в JavaScript есть закрытия для создания объектов, иначе все превратилось бы в кошмар.

avatar
Dmitry Frank
6 мая 2016 в 10:14
21

Ознакомьтесь с иллюстрированным объяснением : Как закрытие JavaScript работает за кулисами .

В статье объясняется, как объекты области (или LexicalEnvironment s) выделяются и используются интуитивно понятным способом. Например, для этого простого скрипта:

"use strict";

var foo = 1;
var bar = 2;

function myFunc() {
  //-- Define local-to-function variables
  var a = 1;
  var b = 2;
  var foo = 3;
}

//-- And then, call it:
myFunc();

При выполнении кода верхнего уровня мы имеем следующее расположение объектов области:

Enter image description here

И когда вызывается myFunc(), мы получаем следующую цепочку областей видимости:

Enter image description here

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

Подробности см. В вышеупомянутой статье.

avatar
Mohammed Safeer
6 мая 2016 в 10:07
18

Следующий пример представляет собой простую иллюстрацию закрытия JavaScript. Это закрывающая функция, которая возвращает функцию с доступом к ее локальной переменной x,

function outer(x){
     return function inner(y){
         return x+y;
     }
}

Вызов функции следующим образом:

var add10 = outer(10);
add10(20); // The result will be 30
add10(40); // The result will be 50

var add20 = outer(20);
add20(20); // The result will be 40
add20(40); // The result will be 60
avatar
Harry Robbins
6 мая 2016 в 10:04
17

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

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

var x = 1;
function myFN() {
  alert(x); //1, as opposed to undefined.
}
// Or
function a() {
   var x = 1;
   function b() {
       alert(x); //1, as opposed to undefined.
   }
   b();
}

И что?

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

Зачем мне это нужно?

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

avatar
Andy
6 мая 2016 в 09:49
24

Вот самый дзен-ответ, который я могу дать:

Что вы ожидаете от этого кода? Расскажите мне в комментарии, прежде чем запускать его. Мне любопытно!

function foo() {
  var i = 1;
  return function() {
    console.log(i++);
  }
}

var bar = foo();
bar();
bar();
bar();

var baz = foo();
baz();
baz();
baz();

Теперь откройте консоль в своем браузере ( Ctrl + Shift + I или <17> код, надеюсь, и вставьте код F1231018746>) и нажмите введите .

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

Я выразился так, потому что, когда я понял, что этот код помещает экземпляры внутренней функции foo() в bar и baz, а затем вызывает их через эти переменные, меня больше ничего не удивило.

Но если я ошибаюсь и вывод консоли вас удивил, дайте мне знать!

avatar
Mayur Randive
6 мая 2016 в 09:32
28

Вот простой сценарий в реальном времени. Просто прочтите его, и вы поймете, как мы использовали здесь закрытие (посмотрите, как меняется номер места).

Все другие примеры, описанные ранее, также очень хорошо подходят для понимания концепции.

function movieBooking(movieName) {
    var bookedSeatCount = 0;
    return function(name) {
        ++bookedSeatCount ;
        alert( name + " - " + movieName + ", Seat - " + bookedSeatCount )
    };
};

var MI1 = movieBooking("Mission Impossible 1 ");
var MI2 = movieBooking("Mission Impossible 2 ");

MI1("Mayur");
// alert
// Mayur - Mission Impossible 1, Seat - 1

MI1("Raju");
// alert
// Raju - Mission Impossible 1, Seat - 2

MI2("Priyanka");
// alert
// Raja - Mission Impossible 2, Seat - 1
avatar
b_dev
6 мая 2016 в 09:29
13

Представьте, что в вашем городе есть очень большой парк, где вы видите фокусника по имени мистер Кодер, который запускает бейсбольные игры в разных уголках парка, используя свою волшебную палочку, называемую JavaScript.

Естественно, каждая игра в бейсбол имеет одни и те же правила, и каждая игра имеет свое собственное табло.

Естественно, результаты одного бейсбольного матча полностью отделены от результатов других игр.

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

avatar
Juan Garcia
5 мая 2016 в 16:32
13

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

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

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

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

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

avatar
Raul Martins
5 мая 2016 в 16:22
10

Учитывая, что вопрос заключается в том, чтобы объяснить это просто так, как если бы 6-летнему , мой ответ был бы:

«Когда вы объявляете функцию в JavaScript, она имеет постоянный доступ ко всем переменным и функциям, которые были доступны в строке до объявления этой функции. Функция и все внешние переменные и функции, к которым у нее есть доступ, являются то, что мы называем закрытием ".

avatar
Arman McHitarian
5 мая 2016 в 16:19
15
( и что еще хуже для них - скучные) вещи для их детских умов :) Если бы мне было шесть лет, я бы не хотел иметь таких родителей или не дружил бы с такими скучными меценатами, извините :)

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

Я действительно не знаю, как объяснить это детям 5-6 лет. Я не думаю, что они оценят любые фрагменты кода JavaScript, например:

function Baby(){
    this.iTrustYou = true;
}

Baby.prototype.hug = function (baby) {
    var smiles = 0;

    if (baby.iTrustYou) {
        return function() {
            smiles++;
            alert(smiles);
        };
    }
};

var
   arman = new Baby("Arman"),
   morgan = new Baby("Morgana");

var hug = arman.hug(morgan);
hug();
hug();

Только для детей:

Закрытие - объятие

Ошибка - это летать

KISS - это поцелуй! :)

avatar
Jim
5 мая 2016 в 16:13
17

Я нашел очень четкий раздел 6 главы 8 «Замыкания» из JavaScript: The Definitive Guide Дэвида Фланагана, 6-е издание, О'Рейли, 2011. Я попытаюсь перефразировать.

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

  2. Область действия функции зависит от места ее объявления, а не места выполнения.

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

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

avatar
srgstm
5 мая 2016 в 16:04
50

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

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

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

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

Пример:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();
avatar
goonerify
5 мая 2016 в 15:05
14

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

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

avatar
dlaliberte
5 мая 2016 в 15:00
374

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

  • Замыкание создается не только тогда, когда вы возвращаете внутреннюю функцию. Фактически, включающая функция вообще не должна возвращать , чтобы его закрытие было создано. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области или передать ее в качестве аргумента другой функции, где ее можно будет вызвать немедленно или в любое время позже. Следовательно, закрытие закрывающей функции, вероятно, создается , как только закрывающая функция вызывается , поскольку любая внутренняя функция имеет доступ к этому закрытию всякий раз, когда вызывается внутренняя функция, до или после возврата закрывающей функции.
  • Замыкание не ссылается на копию старых значений переменных в своей области. Сами переменные являются частью замыкания, поэтому значение, видимое при доступе к одной из этих переменных, является самое последнее значение на момент обращения к нему. Вот почему внутренние функции, созданные внутри циклов, могут быть сложными, поскольку каждая из них имеет доступ к одним и тем же внешним переменным, а не захватывает копию переменных во время создания или вызова функции.
  • «Переменные» в замыкании включают любые именованные функции , объявленные внутри функции. Они также включают аргументы функции. Замыкание также имеет доступ к содержащим его переменным замыкания вплоть до глобальной области видимости.
  • Замыкания используют память, но они не вызывают утечек памяти , поскольку JavaScript сам по себе очищает свои собственные круговые структуры, на которые нет ссылок. Утечки памяти Internet Explorer, связанные с замыканиями, возникают, когда ему не удается отключить значения атрибутов DOM, которые ссылаются на замыкания, тем самым сохраняя ссылки на возможно циклические структуры.
avatar
Brandon Kent
15 марта 2016 в 17:12
9

На мой взгляд, MDN объясняет это лучше всего:

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

У закрытия всегда есть внешняя функция и внутренняя функция. Внутренняя функция - это то место, где происходит вся работа, а внешняя функция - это просто среда, которая сохраняет область, в которой была создана внутренняя функция. Таким образом, внутренняя функция закрытия «запоминает» среду / область видимости, в которой она была создана. Самый классический пример - функция счетчика:

var closure = function() {
  var count = 0;
  return function() {
    count++;
    console.log(count);
  };
};

var counter = closure();

counter() // returns 1
counter() // returns 2
counter() // returns 3

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

avatar
16 января 2016 в 20:49
8

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

Определение из книги Speaking JavaScript «Замыкание - это функция плюс соединение с областью действия, в которой функция была создана» - Доктор Аксель Раушмайер

Так как это может выглядеть? Вот пример

function newCounter() {
  var counter = 0;
   return function increment() {
    counter += 1;
   }
}

var counter1 = newCounter();
var counter2 = newCounter();

counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3

newCounter закрывается при приращении , счетчик можно ссылаться на и получить к нему приращение 92629365349349

counter1 и counter2 будут отслеживать свои собственные значения.

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

avatar
someisaac
16 января 2016 в 02:39
95

Пример для первой точки по dlaliberte:

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
avatar
Magne
16 января 2016 в 02:30
210

Как бы я объяснил это шестилетнему ребенку:

Вы знаете, как взрослые могут владеть домом, и они называют его своим домом? Когда у мамы рождается ребенок, ребенку вообще ничего не принадлежит, верно? Но его родители владеют домом, поэтому всякий раз, когда кто-то спрашивает ребенка: «Где твой дом?», Он / она может ответить «этот дом!» И указать на дом его родителей. «Закрытие» - это способность ребенка всегда (даже если он находится за границей) сказать, что у него есть дом, даже если на самом деле дом принадлежит родителю.

avatar
24 ноября 2015 в 21:21
7

Жил-был пещерный человек

function caveman {

у которого был особенный рок,

var rock = "diamond";

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

return {
    getRock: function() {
        return rock;
    }
};
}

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

var friend = caveman();
var rock = friend.getRock();

Довольно умный пещерный человек.

avatar
4 ноября 2015 в 20:36
2

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

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

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

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

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

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

Таким образом, замыкание - это способ вызова функции без необходимости явно указывать контекст как параметр или как объект при вызове метода.

var closure = createclosure(varForClosure);
closure(param1);  // closure has access to whatever createclosure gave it access to,
                  // including the parameter storing varForClosure.

против

var contextvar = varForClosure; // use a struct for storing more than one..
contextclosure(contextvar, param1);

против

var contextobj = new contextclass(varForClosure);
contextobj->objclosure(param1);

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

avatar
12 октября 2015 в 02:15
6

Лучше всего объяснять эти концепции постепенно:

Переменные

console.log(x);
// undefined

Здесь undefined - это способ JavaScript "Я понятия не имею, что означает x".

Переменные подобны тегам.

Можно сказать, тег x указывает на значение 42:

var x = 42;
console.log(x);
// 42

Теперь JavaScript знает, что означает x.

Вы также можете переназначить переменную.

Сделать тег x указывать на другое значение:

x = 43;
console.log(x);
// 43

Теперь x означает другое.

Область действия

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

function A() {
  var x = 42;
}

console.log(x);

// undefined

Снаружи коробки вы не можете увидеть, что внутри коробки.

Но изнутри коробки вы можете увидеть, что находится за ее пределами:

var x = 42;

function A() {
  console.log(x);
}

// 42

Внутри функции A у вас есть «доступ к области действия» к x.

Теперь, если у вас есть две коробки бок о бок:

function A() {
  var x = 42;
}

function B() {
  console.log(x);
}

// undefined

Внутри функции B у вас нет доступа к переменным внутри функции A.

Но если вы поместите определение функции B внутри функции A:

function A() {

  var x = 42;

  function B() {
    console.log(x);
  }

}

// 42

Теперь у вас есть "доступ к области".

Функции

В JavaScript вы запускаете функцию, вызывая ее:

function A() {
  console.log(42);
}

Как это:

A();

// 42

Функции как значения

В JavaScript вы можете указать тег на функцию, как если бы он указывал на число:

var a = function() {
  console.log(42);
};

Переменная a теперь означает функцию, вы можете ее запустить.

a();
// 42

Вы также можете передать эту переменную:

setTimeout(a, 1000);

Через секунду (1000 миллисекунд) вызывается функция, на которую указывает a:

// 42

Область закрытия

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

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

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

var a = function() {

  var text = 'Hello!'

  var b = function() {
    console.log(text);
    // inside function `b`, you have access to `text`
  };

  // but you want to run `b` later, rather than right away
  setTimeout(b, 1000);

}

Что теперь происходит?

// 'Hello!'

Или рассмотрим это:

var c;

var a = function() {

  var text = 'Hello!'

  var b = function() {
    console.log(text);
    // inside function `b`, you have access to `text`
  };

  c = b;

}

// now we are out side of function `a`
// call `a` so the code inside `a` runs
a(); 

// now `c` has a value that is a function
// because what happened when `a` ran

// when you run `c`
c();

// 'Hello!'

У вас по-прежнему есть доступ к переменным в области закрытия.

Хотя a завершил работу, и теперь вы используете c за пределами a.

То, что здесь только что произошло, в JavaScript называется закрытием .

avatar
TastyCode
15 июля 2015 в 11:47
9

Мне нравится определение замыкания, данное Кайлом Симпсоном:

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

Лексическая область видимости - это когда внутренняя область видимости может получить доступ к своей внешней области.

Вот модифицированный пример, который он приводит в своей серии книг «You Don't Know JS: Scopes & Closures».

function foo() {
  var a = 2;

  function bar() {
    console.log( a );
  }
  return bar;
}

function test() {
  var bz = foo();
  bz();
}

// prints 2. Here function bar referred by var bz is outside 
// its lexical scope but it can still access it
test(); 
avatar
Michael Dziedzic
29 апреля 2015 в 15:37
49

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

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

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ПРЕДУПРЕЖДЕНИЕ: обезьяна

В приведенном выше примере вызывается outerFunction, которая, в свою очередь, вызывает innerFunction. Обратите внимание на то, как externalVar доступен для innerFunction, о чем свидетельствует правильное предупреждение значения outerVar.

Теперь рассмотрим следующее:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ПРЕДУПРЕЖДЕНИЕ: обезьяна

referenceToInnerFunction имеет значение outerFunction (), которое просто возвращает ссылку на innerFunction. Когда вызывается referenceToInnerFunction, она возвращает externalVar. Опять же, как и выше, это демонстрирует, что innerFunction имеет доступ к outerVar, переменной outerFunction. Кроме того, интересно отметить, что он сохраняет этот доступ даже после завершения работы externalFunction.

И вот здесь все становится по-настоящему интересным. Если бы мы избавились от externalFunction, скажем, установили для него значение null, вы могли бы подумать, что referenceToInnerFunction потеряет доступ к значению externalVar. Но это не так.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ПРЕДУПРЕЖДЕНИЕ: обезьяна ПРЕДУПРЕЖДЕНИЕ: обезьяна

Но как это так? Как может referenceToInnerFunction узнать значение externalVar теперь, когда для outerFunction установлено значение null?

Причина, по которой referenceToInnerFunction все еще может получить доступ к значению externalVar, заключается в том, что, когда закрытие было впервые создано путем помещения innerFunction внутри outerFunction, innerFunction добавила ссылку на область видимости outerFunction (ее переменные и функции) в свою цепочку областей видимости. Это означает, что innerFunction имеет указатель или ссылку на все переменные outerFunction, включая externalVar. Таким образом, даже когда externalFunction завершил выполнение или даже если он удален или установлен в значение null, переменные в его области, такие как externalVar, остаются в памяти из-за выдающейся ссылки на них со стороны внутренней функции, которая была возвращена в referenceToInnerFunction. Чтобы действительно освободить externalVar и остальные переменные outerFunction из памяти, вам нужно будет избавиться от этой выдающейся ссылки на них, например, установив для referenceToInnerFunction значение null.

//////////

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

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

ОПОВЕЩЕНИЕ: горилла

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

avatar
Mike Robinson
29 апреля 2015 в 15:36
4

Также ... Возможно, нам следует немного сократить вашего 27-летнего друга, , потому что вся концепция «замыканий» на самом деле (!) ... вуду!

Под этим я подразумеваю: (a) интуитивно вы этого не ожидаете ... И ... (b) когда кто-то находит время, чтобы объяснить это вы, конечно, не ожидаете, что он будет работать!

Интуиция подсказывает вам, что "это должно быть чушь ... конечно это должно привести к какой-то синтаксической ошибке или чему-то еще!" Как, черт возьми, (!) вы могли, по сути, «вытащить функцию из« середины », где бы она ни находилась», чтобы вы могли [по-прежнему!] Иметь доступ для чтения / записи к контекст "wherever-it- было -at ?!"

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

Но сначала нужно будет преодолеть "большое противоречащее интуиции препятствие". Интуиция дает вам множество вполне правдоподобных ожиданий, что такая вещь будет «конечно , абсолютно бессмысленной и, следовательно, совершенно невозможной».

Как я уже сказал: «это вуду».

avatar
Javier La Banca
29 апреля 2015 в 13:43
6

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

 var Closure = (function () {
    // This is a closure
    // Any methods, variables and properties you define here are "private"
    // and can't be accessed from outside the function.

    //This is a private variable
    var foo = "";

    //This is a private method
    var method = function(){

    }
})();

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

var Closure = (function () {
  // This is a closure
  // Any methods, variables and properties you define here are "private"
  // and can't be accessed from outside the function.

  //This is a private variable
  var foo = "";

  //This is a private method
  var method = function(){

  }

  //The method will be accessible from outside the closure
  return {
    method: method
  }

})();

Closure.method();

Надеюсь, это поможет. С уважением,

avatar
29 апреля 2015 в 13:36
21

Замыкание - это функция внутри функции , которая имеет доступ к переменным и параметрам своей «родительской» функции.

Пример:

function showPostCard(Sender, Receiver) {

    var PostCardMessage = " Happy Spring!!! Love, ";

    function PreparePostCard() {
        return "Dear " + Receiver + PostCardMessage + Sender;
    }

    return PreparePostCard();
}
showPostCard("Granny", "Olivia");
avatar
Magne
26 октября 2014 в 10:07
11

Замыкание - это блок кода, который соответствует трем критериям:

  • Его можно передавать как значение и

  • выполняется по требованию любым лицом, имеющим это значение, в это время

  • он может ссылаться на переменные из контекста, в котором он был создан (то есть он закрыт относительно доступа к переменным, в математический смысл слова «закрытый»).

(Слово «закрытие» на самом деле имеет неточное значение, и некоторые люди не думают, что критерий № 1 является частью определения. Я думаю, что это так.)

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

Автор: Пол Кантрелл, @ http://innig.net/software/ruby/closures-in-ruby

avatar
uszywieloryba
26 октября 2014 в 10:05
8

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

В JavaScript это происходит, когда вы:

  • Определите одну функцию внутри другой функции
  • Внутренняя функция вызывается после того, как внешняя функция вернула
// 'name' is resolved in the namespace created for one invocation of bindMessage
// the processor cannot enter this namespace by the time displayMessage is called
function bindMessage(name, div) {

    function displayMessage() {
        alert('This is ' + name);
    }

    $(div).click(displayMessage);
}
avatar
C-link Nepal
26 октября 2014 в 10:01
11

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

Пример:

var outer = function(params){ //Outer function defines a variable called params
    var inner = function(){ // Inner function has access to the params variable of the outer function
        return params;
    }
    return inner; //Return inner function exposing it to outer scope
},
myFunc = outer("myParams");
myFunc(); //Returns "myParams"
avatar
Ravi
26 октября 2014 в 09:58
21

Несмотря на то, что в Интернете существует множество красивых определений замыканий JavaScript, я пытаюсь начать объяснять своему шестилетнему другу мои любимые определения замыкания, которые помогли мне лучше понять замыкание.

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

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

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

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

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

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

Такие языки, как Java, предоставляют возможность объявлять методы закрытыми, что означает, что они могут вызываться только другими методами в том же классе. В JavaScript нет собственного способа сделать это, но можно эмулировать частные методы с помощью замыканий.

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

Замыкания - это механизм абстракции, который позволяет очень четко разделять проблемы.

Использование замыканий:

Замыкания полезны для сокрытия реализации функциональности, но при этом раскрывают интерфейс.

Вы можете эмулировать концепцию инкапсуляции в JavaScript, используя замыкания.

Замыкания широко используются в jQuery и Node.js.

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

Пример закрытия:

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

>

Пример 1. Закрытие достигается за счет возврата функции.

function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

Пример 2: Замыкание достигается здесь путем возврата литерала объекта.

function makeAdder(x) {
    return {
        add: function(y){
            return x + y;
        }
    }
}

var add5 = makeAdder(5);
console.log(add5.add(2));//7

var add10 = makeAdder(10);
console.log(add10.add(2));//12

Пример 3: Замыкания в jQuery

$(function(){
    var name="Closure is easy";
    $('div').click(function(){
        $('p').text(name);
    });
});

Полезные ссылки:

Благодаря приведенным выше ссылкам, которые помогают мне лучше понять и объяснить закрытие.

avatar
cube
26 октября 2014 в 09:43
6

Слово закрытие просто означает возможность доступа к объектам (шестилетний: вещи), которые закрыты ( : private) в функции (шестилетняя: box). Даже если функция (шестилетняя: коробка) выходит за пределы области действия (шестилетней давности: отправлена ​​далеко).

avatar
Tahir Chad
26 октября 2014 в 09:42
6

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

Это похоже на нанесение покрытия на функцию.

Итак, шестилетнему ребенку это можно объяснить, проведя аналогию. Допустим, я строю робота. Этот робот может многое. Среди прочего, я запрограммировал его на подсчет количества птиц, которых он видит в небе. Каждый раз, когда он видел 25 птиц, он должен сказать мне, сколько птиц он видел с самого начала.

Я не знаю, сколько птиц он видел, если он не сказал мне. Только он знает. Это частная сфера. Это в основном память робота. Допустим, я дал ему 4 ГБ.

Возвращенная функция - сообщить мне, сколько птиц он видел. Я тоже это создал.

Эта аналогия немного отстойна, но я думаю, кто-то может ее улучшить.

avatar
Vitim.us
26 октября 2014 в 09:40
22

Учитывая следующую функцию

function person(name, age){

    var name = name;
    var age = age;

    function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }

    return introduce;
}

var a = person("Jack",12);
var b = person("Matt",14);

Каждый раз, когда вызывается функция person, создается новое замыкание. Хотя переменные a и b имеют одну и ту же функцию introduce, они связаны с разными замыканиями. И это закрытие будет существовать даже после того, как функция person завершит выполнение.

Enter image description here

a(); //My name is Jack, and I'm 12
b(); //My name is Matt, and I'm 14

Абстрактные замыкания можно представить примерно так:

closure a = {
    name: "Jack",
    age: 12,
    call: function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }
}

closure b = {
    name: "Matt",
    age: 14,
    call: function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }
}

Если вы знаете, как работает class на другом языке, я проведу аналогию.

Думайте как

  • JavaScript function как constructor
  • local variables как instance properties
  • эти properties частные
  • inner functions как instance methods

Каждый раз, когда function вызывается

  • Будет создан новый object, содержащий все локальные переменные.
  • Методы этого объекта имеют доступ к "properties" этого экземпляра объекта.
avatar
Stupid Stupid
25 октября 2014 в 23:02
50

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

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

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Еще один очень простой способ объяснить это с точки зрения объема:

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

avatar
Charlie
25 октября 2014 в 22:59
30

Для шестилетнего ребенка?

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

000001 (jamiesHouse)

Через месяц вы и ваша семья переезжаете из Ann Ville в следующий город, но вы и ваш друг по-прежнему поддерживаете связь, поэтому теперь вам нужно набрать код города, в котором живет ваш друг, прежде набрав их «правильный» номер:

001 000001 (annVille.jamiesHouse)

Через год ваши родители переезжают в совершенно новую страну, но вы и ваш друг все еще поддерживаете связь, поэтому после того, как вы попросили родителей разрешить вам звонить за границу, вы набираете:

01 001 000001 (myOldCountry.annVille.jamiesHouse)

Как ни странно, после переезда в новую страну вы и ваша семья случайно переехали в новый город под названием Энн Вилль ... и вы случайно подружились с каким-то новым человеком по имени Джейми ... Ты позвоните им ...

000001 (jamiesHouse)

Жуткий ...

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

  • «Правда? Еще один Джейми? В Энн Вилле? В вашей новой стране !!?»
  • "Ага ... Давай назовем их ..."

02 001 000001 (myNewCountry.annVille.jamiesHouse)

Мнения?

Более того, у меня есть масса вопросов о терпении современного шестилетнего ребенка ...

avatar
Peter Mortensen
25 октября 2014 в 22:57
15

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

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

avatar
mjmoody383
25 октября 2014 в 22:54
46

Я бы просто указал им на страницу Mozilla Closures. Это лучшее, наиболее краткое и простое объяснение основ замыкания и практического использования, которое я нашел. Настоятельно рекомендуется всем, кто изучает JavaScript.

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

avatar
dmi3y
25 октября 2014 в 22:52
57

Хорошо, разговаривая с 6-летним ребенком, я бы, возможно, использовал следующие ассоциации.

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

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

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

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Как видите, игрушки, оставленные в комнате, по-прежнему доступны через брата, независимо от того, заперта ли комната. Вот jsbin, чтобы поиграть с ним.

avatar
Nathan Whitehead
25 октября 2014 в 22:38
138

Я составил интерактивное руководство по JavaScript, чтобы объяснить, как работают замыкания. Что такое закрытие?

Вот один из примеров:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here
avatar
1 июня 2014 в 20:39
6

Я считаю полезным сделать шаг назад и изучить более общее понятие «замыкания» - так называемый «оператор соединения».

В математике оператор «соединения» - это функция частично упорядоченного набора, которая возвращает наименьший объект, больший или равный его аргументам. В символах соедините [a, b] = d так, чтобы d> = a и d> = b, но не существует e, такого, что d> e> = a или d> e> = b.

Таким образом, объединение дает вам наименьшее, что "больше" частей.

Теперь обратите внимание, что области JavaScript представляют собой частично упорядоченную структуру. Так что есть разумное понятие соединения. В частности, объединение областей видимости - это наименьшая область действия, превышающая исходные. Эта область называется закрытием .

Таким образом, замыкание для переменных a, b, c - это наименьшая область видимости (в решетке областей видимости вашей программы!), Которая вводит a, b и c в область видимости.

avatar
28 марта 2014 в 19:45
6

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

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

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

Один из лучших (или самый простой) - это пересказ примера Морриса "Закрытие для чайников".

Если взять концепцию SayHi2Bob всего на один шаг дальше, демонстрируются две основные вещи, которые вы можете почерпнуть, прочитав все ответы:

  1. Замыкания имеют доступ к переменным содержащейся функции.
  2. Замыкания сохраняются в собственном пространстве памяти (и, таким образом, полезны для всех видов инстанцирования oop-y)

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

http://jsfiddle.net/9ZMyr/2/

function sayHello(name) {
  var text = 'Hello ' + name; // Local variable
  console.log(text);
  var sayAlert = function () {
      alert(text);
  }
  return sayAlert;
}

sayHello(); 
/* This will write 'Hello undefined' to the console (in Chrome anyway), 
but will not alert though since it returns a function handle to nothing). 
Since no handle or reference is created, I imagine a good js engine would 
destroy/dispose of the internal sayAlert function once it completes. */

// Create a handle/reference/instance of sayHello() using the name 'Bob'
sayHelloBob = sayHello('Bob');
sayHelloBob();

// Create another handle or reference to sayHello with a different name
sayHelloGerry = sayHello('Gerry');
sayHelloGerry();

/* Now calling them again demonstrates that each handle or reference contains its own 
unique local variable memory space. They remain in memory 'forever' 
(or until your computer/browser explode) */
sayHelloBob();
sayHelloGerry();

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

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

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

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

avatar
mykhal
18 декабря 2013 в 16:48
168

Википедия о закрытии:

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

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

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

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

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

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

avatar
28 января 2013 в 02:23
243

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

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

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

Полная запись:

Так что же это за закрывающиеся штуки?

avatar
4 января 2013 в 14:27
14

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

// A function that generates a new function for adding numbers.
function addGenerator( num ) {
    // Return a simple function for adding two numbers
    // with the first number borrowed from the generator
    return function( toAdd ) {
        return num + toAdd
    };
}

// addFive now contains a function that takes one argument,
// adds five to it, and returns the resulting number.
var addFive = addGenerator( 5 );
// We can see here that the result of the addFive function is 9,
// when passed an argument of 4.
alert( addFive( 4 ) == 9 );
avatar
4 января 2013 в 14:25
20

Из личного сообщения блога:

По умолчанию JavaScript поддерживает два типа областей: глобальную и локальную.

var a = 1;

function b(x) {
    var c = 2;
    return x * c;
}

В приведенном выше коде переменная a и функция b доступны из любого места кода (то есть глобально). Переменная c доступна только в пределах области действия функции b (то есть локально). Большинство разработчиков программного обеспечения не будут довольны этим недостатком гибкости области видимости, особенно в больших программах.

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

function a(x) {
    return function b(y) {
        return x + y;
    }
}

Здесь функция a возвращает функцию с именем b. Поскольку b определен в a, он автоматически получает доступ ко всему, что определено в a, то есть x в этом примере. Вот почему b может возвращать x + y без объявления x.

var c = a(3);

Переменной c назначается результат вызова с параметром 3. То есть, экземпляр функции b, где x = 3. Другими словами, c теперь является функцией, эквивалентной :

var c = function b(y) {
    return 3 + y;
}

Функция b запоминает, что x = 3 в своем контексте. Следовательно:

var d = c(4);

присвоит значение 3 + 4 d, то есть 7.

Примечание : Если кто-то изменит значение x (скажем, x = 22) после создания экземпляра функции b, это также будет отражено в b. Следовательно, более поздний вызов c (4) вернет 22 + 4, то есть 26.

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

(function () {
    var f = "Some message";
    alert(f);
})();

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

Теперь есть распространенное предостережение JavaScript, где могут помочь замыкания:

var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(x) { return x + i ; }
}

Из вышесказанного большинство может предположить, что массив a будет инициализирован следующим образом:

a[0] = function (x) { return x + 0 ; }
a[1] = function (x) { return x + 1 ; }
a[2] = function (x) { return x + 2 ; }

На самом деле, именно так инициализируется a, поскольку последнее значение i в контексте равно 2:

a[0] = function (x) { return x + 2 ; }
a[1] = function (x) { return x + 2 ; }
a[2] = function (x) { return x + 2 ; }

Решение:

var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(tmp) {
        return function (x) { return x + tmp ; }
    } (i);
}

Аргумент / переменная tmp содержит локальную копию изменяющегося значения i при создании экземпляров функции.

avatar
24 декабря 2012 в 11:10
93

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

avatar
20 июля 2011 в 15:16
84

Вы спите и приглашаете Дэна. Вы говорите Дэну принести один контроллер XBox.

Дэн приглашает Пола. Дэн просит Пола принести один контроллер. Сколько контроллеров было привлечено на вечеринку?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");