Понимание закрытия в Javascript - PullRequest
7 голосов
/ 11 октября 2010

Я пытаюсь обернуть голову вокруг замыканий в Javascript.

Вот пример из учебника:

function greeter(name, age) {
  var message = name + ", who is " + age + " years old, says hi!";

  return function greet() {
    console.log(message);
  };
}

// Generate the closure
var bobGreeter = greeter("Bob", 47);

// Use the closure
bobGreeter();

Автор сказал, что это эффективный способ использованиязакрытие для создания приватных переменных, но я не понимаю, в чем дело.

Может ли кто-нибудь осветить преимущества кодирования, как это?

Ответы [ 8 ]

23 голосов
/ 11 октября 2010

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

Приведенный вами пример демонстрирует это достаточно хорошо.Я добавил встроенные комментарии для объяснения окружения.

// Outside, we begin in the global environment.
function greeter(name, age) {
  // When greeter is *invoked* and we're running the code here, a new
  // environment is created. Within this environment, the function's arguments
  // are bound to the variables `name' and `age'.

  // Within this environment, another new variable called `message' is created.
  var message = name + ", who is " + age + " years old, says hi!";

  // Within the same environment (the one we're currently executing in), a
  // function is defined, which creates a new closure that references this
  // environment. Thus, this function can access the variables `message', `name',
  // and `age' within this environment, as well as all variables within any
  // parent environments (which is just the global environment in this example).
  return function greet() { console.log(message); };
}

Когда выполняется var bobGreeter = greeter("Bob", 47);, создается новое замыкание;то есть теперь у вас есть новый экземпляр функции вместе со средой, в которой он был создан.Следовательно, ваша новая функция имеет ссылку на переменную `message 'в указанной среде, хотя никто другой не делает.

Дополнительное чтение: SICP Ch 3.2 .Хотя он фокусируется на схеме, идеи совпадают.Если вы хорошо понимаете эту главу, у вас будет хорошее представление о том, как работают среды и лексические области действия.

В Mozilla также есть страница, посвященная , объясняющая закрытие .

8 голосов
/ 11 октября 2010

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

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

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

var a = "before";
var f = function(value) {
    return function()
    {
      alert(value);
    }
} (a); //here I am creating a closure, which makes my inner function no longer depend on this global variable
a = "after";

f(); //prints "before"

Теперь, зачем вам это нужно?Ну, вот практический пример.Рассмотрим следующий код, который использует jQuery для добавления 5 ссылок на документ.Когда вы нажимаете на ссылку, вы ожидаете, что она наберет alert номер, связанный с этой ссылкой, поэтому, щелкнув по первому значению, вы увидите 0, и так далее. Но, это не так, каждая ссылка alert будет иметь значение 5. Это потому, что определяемая мной функция зависит от переменной i, которая изменяется вне контекста функции.Функция, которую я передаю в bind, является функцией швейцарского сыра.

for (var i = 0; i < 5; i++)
{
    var a = $('<a>test link</a>').bind('click', function(){
        alert(i);
    });
    $(a).appendTo('body');
}

Теперь давайте исправим это, создав закрытие, чтобы каждая ссылка alert имела свой правильный номер.

for (var i = 0; i < 5; i++)
{
    var fn = function (value) {
        return function() {
            alert(value);
        };
    } (i); //boom, closure
    var a = $('<a>test link</a>').bind('click', fn);
    $(a).appendTo('body');
}
4 голосов
/ 11 октября 2010

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

Как насчет следующего примера?

function make_counter(){
    var i =0;
    return function(){
        return ++i;
    }
}

var a = make_counter();
console.log(a());  // 1
console.log(a());  // 2
var b = make_counter();
console.log(b());  // 1
console.log(a());  // 3
2 голосов
/ 11 октября 2010

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

function makeThresholdFunction(threshold) {
    return function(param) {
        return (param > threshold);
    }

}

var goodEnough = makeThresholdFunction(0.5);
...
if (goodEnough(calculatedPrecision)) {
   ...
}

С крышками вы также можете использовать все приемы с такими функциями, как их композиция:

function compose(f1, f2) {
    return function(arg) {
        return f1(f2(arg));
    }
}

var squareIncremented = compose(square, inc);
squareIncremented(5); // 36

Подробнее о дизайне крышеки использование можно найти на SICP .

2 голосов
/ 11 октября 2010

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

Я думаю, что область действия функции в Javascript может быть выражена довольно кратко.

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

2 голосов
/ 11 октября 2010

Лучшим примером может быть

function add(start, increment) {
  return function() {
      return start += increment;
  }
}

var add1 = add(10, 1);

alert(add1()); // 11
alert(add1()); // 12

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

Возвращенная функция все еще имеет доступ к своейродительские переменные (в данном случае start и increment).

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

1 голос
/ 29 декабря 2015
//Lets start with a basic Javascript snippet
function generateCash() {
    var denomination = [];
    for (var i = 10; i < 40; i += 10) {
        denomination.push(i);
    }
    return denomination;
}

Это базовый оператор функции в Javascript, который возвращает массив [10,20,30]

//--Lets go  a step further

function generateCash() {
    var denomination = [];
    for (var i = 10; i < 40; i += 10) {
        denomination.push(console.log(i));
    }
    return denomination;
}

. Это будет печатать 10, 20, 30 последовательно во время итерации цикла, но будетвернуть массив [undefined, undefined, undefined], основная причина в том, что мы не выдвигаем фактическое значение i, мы просто распечатываем его, следовательно, на каждой итерации движок javascript будет устанавливать его как undefined.

//--Lets dive into closures

function generateCash() {
    var denomination = [];
    for (var i = 10; i < 40; i += 10) {
        denomination.push(function() {
            console.log(i)
        });
    }
    return denomination;
}

var dn = generateCash();
console.log(dn[0]());
console.log(dn[1]());
console.log(dn[2]());

Это немного сложно, как вы ожидаете, будет ли результат, [10,20,30]?Ответы нет, давайте посмотрим, как это происходит.Сначала создается глобальный контекст выполнения, когда мы создаем dn, также у нас есть функция generatecash ().Теперь мы видим, что, поскольку цикл for повторяется, он создает три объекта анонимной функции, и может возникнуть соблазн думать, что console.log внутри функции push тоже запускается, но на самом деле это не так.Мы вызывали generateCash (), поэтому функция push просто создает три объекта анонимной функции, но не вызывает функцию.В конце итерации текущий локальный контекст извлекается из стека выполнения и покидает состояние i: 40 и arr: [functionobj0 (), functionob1 (), functionobj2 ()]. ​​

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

1 голос
/ 04 мая 2011

Я нашел эту статью довольно полезной.

Когда функция не функция?

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