Как эта функция закрытия JavaScript повторно использует объект без глобальной переменной?

Я решил сделать один шаг вперед, пытаясь понять Javascript и снова прочитать Javascript: The Good Parts. И здесь возникает первое сомнение:

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

var digit_name = function(n) {
 var names = ['zero','one','two','three'];
 return names[n];
}

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

var digit_name = function () {
  var names = ['zero', 'one', 'two', 'three'];
  return function (n) {
    return names[n];
  }
}();

Это делает переменную names сохраненной в памяти, и поэтому она не получает экземпляр при каждом вызове digit_name.

Я хочу знать, почему? Когда мы вызываем digit_name, почему первая строка игнорируется? Что мне не хватает? Что на самом деле происходит здесь?

Я основал этот пример не только в книге, но и в этом видео (минута 26)

(если кто-то думает о лучшем титуле, пожалуйста, предложите, если это необходимо...)

Ответ 1

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

var digit_name = (function () {
  var names = ['zero', 'one', 'two', 'three'];
  return function (n) {
    return names[n];
  }
})();

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

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

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

digit_name таким образом становится:

function (n) {
  //'names' is available inside this function because 'names' is 
  //declared outside of this function, one level up the scope chain
  return names[n];
}

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

Что касается эффективности:

Второй пример более эффективен, потому что names объявляется только один раз - когда запускается функция самозапуска (т.е. var digit_name = (function() {...})();). Когда вызывается digit_names, он будет искать цепочку областей действия до тех пор, пока не найдет names.

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

Графический пример:

Пример, который вы предоставили от Дугласа Крокфорда, - довольно сложный пример, когда вы начинаете с изучения того, как работают замыкания и сферы действия - много материала, упакованного в крошечный код. Я бы рекомендовал взглянуть на визуальное объяснение закрытия, например: http://www.bennadel.com/blog/1482-A-Graphical-Explanation-Of-Javascript-Closures-In-A-jQuery-Context.htm

Ответ 2

Это не ответ, а пояснение в случае, если приведенные примеры все еще кажутся запутанными.

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

var digit_name = (
    function () { // <------------------- digit name is not this function

        var names = ['zero', 'one', 'two', 'three'];

        return function (n) { // <------- digit name is really this function
            return names[n];
        }
    }
)();

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

function digit_name_maker () {
    var names = ['zero', 'one', 'two', 'three'];

    return function (n) {
        return names[n];
    }
}

var digit_name = digit_name_maker(); // digit_name is now a function

Что вы должны заметить, так это то, что даже если массив names определен в функции digit_name_maker, он все еще доступен в функции digit_name. В принципе обе функции разделяют этот массив. В основном это замыкания: переменные, разделяемые между функциями. Мне нравится думать о ней как о частной частной переменной - она ​​похожа на глобальные переменные, поскольку все функции имеют общий доступ к ней, но код за пределами закрытия не может ее увидеть.

Ответ 3

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

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


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

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