Замыкания Javascript — каковы недостатки?

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

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

Преимущества закрытия:

  1. Инкапсулируйте переменные в локальную область с помощью внутренней функции. Анонимность функции незначительна.

Что я нашел полезным, так это провести базовое тестирование в отношении локальной/глобальной области:

<script type="text/javascript">

   var global_text  = "";
   var global_count = 0;
   var global_num1  = 10;
   var global_num2  = 20;
   var global_num3  = 30;

   function outerFunc() {

      var local_count = local_count || 0;

      alert("global_num1: " + global_num1);    // global_num1: undefined
      var global_num1  = global_num1 || 0;
      alert("global_num1: " + global_num1);    // global_num1: 0

      alert("global_num2: " + global_num2);    // global_num2: 20
      global_num2  = global_num2 || 0;         // (notice) no definition with 'var'
      alert("global_num2: " + global_num2);    // global_num2: 20
      global_num2  = 0;

      alert("local_count: " + local_count);    // local_count: 0

      function output() {
         global_num3++;

         alert("local_count:  " + local_count  + "\n" +
               "global_count: " + global_count + "\n" +
               "global_text:  " + global_text
              );

         local_count++; 
      }

      local_count++;
      global_count++;

      return output;  
   }  

   var myFunc = outerFunc();

   myFunc();
      /* Outputs:
       **********************
       * local_count:  1
       * global_count: 1
       * global_text: 
       **********************/

   global_text = "global";
   myFunc();
      /* Outputs:
       **********************
       * local_count:  2
       * global_count: 1
       * global_text:  global
       **********************/

   var local_count = 100;
   myFunc();
      /* Outputs:
       **********************
       * local_count:  3
       * global_count: 1
       * global_text:  global
       **********************/


   alert("global_num1: " + global_num1);      // global_num1: 10
   alert("global_num2: " + global_num2);      // global_num2: 0
   alert("global_num3: " + global_num3);      // global_num3: 33

</script>

Интересные вещи, которые я извлек из этого:

  1. Оповещения в externalFunc вызываются только один раз, когда вызов externalFunc назначается myFunc (myFunc = externalFunc()). Это назначение, кажется, держит externalFunc открытым, в том, что я хотел бы назвать постоянным состоянием.

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

  3. Что действительно интересно, так это локализация, которая происходит при определении локальных переменных. Обратите внимание на разницу в первом предупреждении между global_num1 и global_num2: даже до того, как переменная пытается быть создана, global_num1 считается неопределенным, поскольку «var» использовалась для обозначения локальной переменной для этой функции. -- Об этом уже говорилось ранее, в порядке работы движка Javascript, просто приятно видеть, как это работает.

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

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

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

Единственное, что меня беспокоит, это негативное влияние замыканий на память. Поскольку он сохраняет среду функций открытой, он также хранит эти переменные в памяти, что может иметь или не иметь последствий для производительности, особенно в отношении обхода DOM и сборки мусора. Я также не уверен, какую роль это будет играть с точки зрения утечки памяти, и я не уверен, что замыкание можно удалить из памяти простым «удалить myFunc;».

Надеюсь, это поможет кому-то,

воль7рон


person vol7ron    schedule 17.06.2010    source источник
comment
Я готовлю попкорн для этого.   -  person Pointy    schedule 17.06.2010


Ответы (3)


Вы можете получить множество хороших ответов. Одним из определенных минусов является утечка циклической ссылки Internet Explorer. По сути, «круговые» ссылки на объекты DOM не распознаются JScript как собираемые. С помощью замыканий легко создать то, что IE считает циклической ссылкой. Несколько примеров приведены во второй ссылке.

В IE6 единственный способ освободить память — завершить весь процесс. В IE7 они улучшили его так, что когда вы уходите с рассматриваемой страницы (или закрываете ее), память освобождается. В IE8 объекты DOM лучше понимаются JScript и собираются так, как вы ожидаете.

Предлагаемый обходной путь для IE6 (кроме завершения процесса!) — не использовать замыкания.

person Ken Redler    schedule 17.06.2010
comment
Ответов было не так много, поэтому вы получаете голосование. Однако я не слишком беспокоюсь о IE6. Согласно (w3schools.com/browsers/browsers_stats.asp), он по-прежнему используется 7%, но я полагаю, что в ближайшие месяцы он резко уменьшится. Правительство вносит огромный вклад в более старые версии IE, и как только они начнут использовать новую версию, многие частные подрядчики, вероятно, также перестанут ее поддерживать/использовать. Я думаю, что за последние пару месяцев многие государственные учреждения перешли на более новый, более безопасный браузер, особенно после угрозы Adobe/IE6. - person vol7ron; 29.06.2010
comment
Я тоже немного удивлен отсутствием ответов. Я понимаю вашу точку зрения на IE6; но, к сожалению, я все еще вижу около 25% IE6 среди посетителей корпоративных веб-приложений, над которыми я работаю. Некоторые из этих крупных предприятий настолько консервативны, что до сих пор получают невозвратные затраты от своих инвестиций в Windows XP и IE6 вместе с ней. И они в той или иной степени отягощают прогрессивные усилия веб-продавцов, которые, тем не менее, все еще заинтересованы в своем бизнесе. Я действительно надеюсь, что в ближайшие месяцы он утихнет, но я не удивлюсь, если в 2015 году крупные компании все еще будут использовать IE6. - person Ken Redler; 29.06.2010

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

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

JavaScript без замыканий имеет три* области видимости переменных: на уровне блоков, на уровне функций и на глобальном уровне. Область действия на уровне объекта отсутствует. Без замыканий вы знаете, что переменная объявлена ​​либо в текущей функции, либо в глобальном объекте (потому что именно там живут глобальные переменные).

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

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


Рассмотрим этот пример:

function ScopeIssues(count) {
    var funcs = [];
    for (var i = 0; i < count; ++i) {
        funcs[i] = function() { console.log(i); }
    }
    return funcs;
}

Короткий, прямолинейный... и почти наверняка сломанный. Смотреть:

x = ScopeIssues(10);

x[0]();   // outputs 10
x[1]();   // does too
x[2]();   // same here
x[3]();   // guess

Каждая функция в массиве выводит count. Что тут происходит? Вы видите последствия сочетания замыканий с непониманием закрытых переменных и области видимости.

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

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

function Corrected(count) {
    var funcs = [];
    for (var i = 0; i < count; ++i) {
        (function(which) {
            funcs[i] = function() { console.log(which); };
        })(i);
    }
    return funcs;
}

x = Corrected(10);

x[0]();  // outputs 0
x[1]();  // outputs 1
x[2]();  // outputs 2
x[3]();  // outputs 3

Начиная с ES7, вы можете использовать let вместо var, и каждая итерация цикла в основном будет получать свою собственную версию i.

function WorksToo(count) {
    var funcs = [];
    for (let i = 0; i < count; ++i) {
        funcs[i] = function() { console.log(i); }
    }
    return funcs;
}

x = WorksToo(10);

x[0]();  // outputs 0
x[1]();  // outputs 1
x[2]();  // outputs 2
x[3]();  // outputs 3

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


Другой пример:

value = 'global variable';

function A() {
    var value = 'local variable';
    this.value = 'instance variable';
    (function() { console.log(this.value); })();
}

a = new A();  // outputs 'global variable'

this и arguments разные; в отличие от почти всего остального, они не разделены между границами замыкания?. Каждый вызов функции переопределяет их — и если вы не вызовете функцию, как

  • obj.func(...),
  • func.call(obj, ...),
  • func.apply(obj, [...]), or
  • var obj_func = func.bind(obj); obj_func(...)

чтобы указать this, вы получите значение по умолчанию для this: глобальный объект.^

Самая распространенная идиома, позволяющая обойти проблему this, — это объявить переменную и установить для нее значение this. Наиболее распространенные имена, которые я видел, это that и self.

function A() {
    var self = this;
    this.value = 'some value';
    (function() { console.log(self.value); })();
}

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

function X() {
    var self = this;
    var Y = function() {
        var outer = self;
        var self = this;
    };
}

из-за подъема. JavaScript эффективно перемещает все объявления переменных в начало функции. Это делает приведенный выше код эквивалентным

function X() {
    var self, Y;
    self = this;
    Y = function() {
        var outer, self;
        outer = self;
        self = this;
    };
}

self уже является локальной переменной до запуска outer = self, поэтому outer получает локальное значение, которое на данный момент равно undefined. Вы только что потеряли ссылку на внешний self.


* Начиная с ES7. Раньше их было всего два, а переменные было еще проще отследить. :Р

<суп>? Функции, объявленные с использованием лямбда-синтаксиса (новый для ES7), не переопределяют this и arguments. Что потенциально еще больше усложняет дело.

^ Более новые интерпретаторы поддерживают так называемый «строгий режим»: опциональную функцию, которая направлена ​​на то, чтобы определенные сомнительные шаблоны кода либо полностью терпели неудачу, либо вызывали меньший ущерб. В строгом режиме this по умолчанию равно undefined, а не глобальному объекту. Но это все же совсем другое значение, чем вы обычно намеревались возиться.

person cHao    schedule 28.07.2013
comment
Очень мило, но я думаю, что у вас есть небольшая опечатка в последнем бите. outer уже является локальной переменной... Я думаю, что outer должно быть self? - person Alexis King; 28.07.2013
comment
@JakeKing: Упс... исправлено. Спасибо :) - person cHao; 28.07.2013
comment
проголосуйте за наглядный пример подъема, вызывающего неожиданное поведение. - person Dan Percival; 28.02.2014

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

Я не уверен, как Chrome обрабатывает закрытие. Я думаю, что они на одном уровне с Mozilla, но я не хочу говорить наверняка. IE8 определенно лучше предыдущих версий IE - это почти полностью новый браузер, есть еще некоторые нюансы.

Вы также должны протестировать код, чтобы увидеть, есть ли какое-либо улучшение в скорости.

person Community    schedule 17.06.2010