Javascript Closures - Какие негативы? - PullRequest
10 голосов
/ 17 июня 2010

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

Я немного читал о замыканиях 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 = outerFunc ()).Похоже, что это назначение сохраняет externalFunc открытым в том, что я хотел бы назвать постоянным состоянием.

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

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

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

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

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

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

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

vol7ron

Ответы [ 3 ]

6 голосов
/ 28 июля 2013

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

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

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

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

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


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

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

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

x = ScopeIssues(100);

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

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

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

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

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

x = Corrected(100);

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

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

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, [...]) или
  • 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. Раньше их было всего два, а переменные было еще проще отследить. : P

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

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

6 голосов
/ 17 июня 2010

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

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

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

0 голосов
/ 17 июня 2010

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

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

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

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