Javascript Closures - Какви са негативите?

Въпрос: Изглежда, че има много предимства на Closures, но какви са отрицателните (изтичане на памет? проблеми с обфускацията? увеличаване на честотната лента?)? Освен това, правилно ли разбирам Closures? И накрая, след като бъдат създадени затваряния, могат ли те да бъдат унищожени?

Четох малко за Javascript Closures. Надявам се някой малко по-запознат да насочи моите твърдения и да ме коригира там, където греша.

Предимства от затварянето:

  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. Сигналите в outerFunc се извикват само веднъж, когато извикването на outerFunc е присвоено на myFunc (myFunc = outerFunc()). Това присвояване изглежда поддържа outerFunc отворен, в това, което бих искал да нарека постоянно състояние.

  2. Всеки път, когато се извика myFunc, връщането се изпълнява. В този случай връщането е вътрешната функция.

  3. Нещо наистина интересно е локализацията, която възниква при дефиниране на локални променливи. Забележете разликата в първото предупреждение между global_num1 и global_num2, дори преди променливата да се опита да бъде създадена, global_num1 се счита за недефиниран, тъй като 'var' е използван за обозначаване на локална променлива за тази функция. -- Това беше говорено преди, в реда на работа за двигателя на Javascript, просто е хубаво да видим това да работи.

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

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

Знам, че има и други примери за затваряне, много от които използват анонимни функции в комбинация с циклични структури, но мисля, че това е добре за курс за 101 начинаещи, за да видите ефектите.

Единственото нещо, което ме тревожи, е отрицателното въздействие, което затварянията ще имат върху паметта. Тъй като поддържа функционалната среда отворена, тя също така запазва тези променливи, съхранени в паметта, което може/може да няма отражение върху производителността, особено по отношение на преминаването на DOM и събирането на боклука. Също така не съм сигурен каква роля ще играе това по отношение на изтичането на памет и не съм сигурен дали затварянето може да бъде премахнато от паметта чрез просто „изтриване на myFunc;.“

Надявам се това да помогне на някого,

vol7ron


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 заедно с тях. И те натежават в различна степен прогресивните усилия на уеб базираните доставчици, които въпреки това все още искат своя бизнес. Наистина се надявам да умре през следващите месеци, но няма да бъда шокиран, ако има големи фирми, които все още работят с IE6 през 2015 г. - person Ken Redler; 29.06.2010

Затварянето носи много ползи... но също така и редица проблеми. Същото нещо, което ги прави мощни, ги прави и доста способни да направят бъркотия, ако не внимавате.

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

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

Със затварянето вече нямате тази увереност. Всяка вложена функция въвежда друго ниво на обхват и всички затваряния, създадени в тази функция, виждат (предимно) същите променливи като съдържащата функция. Големият проблем е, че всяка функция може да дефинира свои собствени променливи по желание, които скриват външните.

Правилното използване на затваряния изисква вие (a) да сте наясно как затварянията и var влияят на обхвата и (b) да следите в кой обхват са вашите променливи. В противен случай променливите могат да бъдат споделени случайно (или псевдопроменливите да бъдат загубени!) и могат да последват всякакви глупости.


Помислете за този пример:

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. Преди имаше само две и променливите бяха още по-лесни за проследяване. :P

? Функциите, декларирани чрез ламбда синтаксис (ново за 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