Научете повече за затварянията в JavaScript. Затваряния на JavaScript Създаване на затваряния в цикъл: Много често срещана грешка

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

Както знаете, в JavaScript обхватът на локалните променливи (деклариран с думата var)е тялото на функцията, в която са дефинирани.

Ако декларирате функция в друга функция, първата има достъп до променливите и аргументите на последната:

Код: функция outerFn(myArg) (
var myVar;
функция innerFn() (
//има достъп до myVar и myArg
}
}

Освен това такива променливи продължават да съществуват и остават достъпни за вътрешната функция дори след като външната функция, в която са дефинирани, е била изпълнена.

Нека да разгледаме пример - функция, която връща броя на собствените си извиквания:

Код: функция createCounter() (
var numberOfCalls = 0;
функция за връщане () (
return ++numberOfCalls;
}
}
var fn = createCounter();
fn(); //1
fn(); //2
fn(); //3

В този пример функцията, върната от createCounter, използва променливата numberOfCalls, която запазва желаната стойност между извикванията (вместо незабавно прекратяване, когато createCounter се върне).

Именно за тези свойства такива „вложени“ функции в JavaScript се наричат ​​затваряния (термин, който идва от функционалните езици за програмиране) – те „затварят“ променливите и аргументите на функцията, в която са дефинирани.

Прилагане на затваряния

Нека малко опростим примера по-горе - премахнете необходимостта от отделно извикване на функцията createCounter, като я направите анонимна и я извикате веднага след нейната декларация:

Код: var fn = (function() (
var numberOfCalls = 0;
функция за връщане () (
връщане ++ numberOfCalls;
}
})();

Този дизайн ни позволи да свържем данни към функция, която продължава между своите извиквания - това е едно от приложенията на затварянията. С други думи, с тяхна помощ можем да създаваме функции, които имат собствено променливо състояние.

Друга добра употреба на затварянията е за създаване на функции, които от своя страна също създават функции - това, което някои биха нарекли така наречената техника. метапрограмиране.
Например:

Код: var createHelloFunction = функция(име) (
функция за връщане () (
alert("Здравей, " + име);
}
}
var sayHelloHabrahabr = createHelloFunction("Habrahabr");
sayHelloHabrahabr(); //предупреждава "Здравей, Habrahabr"

Благодарение на затварянето, върнатата функция „помни“ параметрите, предадени на създаващата функция, което е това, от което се нуждаем за този вид неща.

Подобна ситуация възниква, когато не връщаме вътрешна функция, а я закачваме за някакво събитие - тъй като събитието се случва след изпълнението на функцията, затварянето отново помага да не загубим данните, предадени при създаването на манипулатора.

Нека разгледаме един малко по-сложен пример - метод, който свързва функция към конкретен контекст (т.е. обектът, към който думата this ще сочи в него).

Код: Function.prototype.bind = функция(контекст) (
var fn = това;
функция за връщане () (
връщане fn.apply(контекст, аргументи);
};
}
var HelloPage = (
име: "Хабрахабр",
init: функция() (
alert("Здравей, " + this.name);
}
}
//window.onload = HelloPage.init; //предупреждение недефинирано, защото това ще сочи към прозореца
window.onload = HelloPage.init.bind(HelloPage); // сега всичко работи

В този пример, използвайки затваряния, функцията, върната от bind, запомня първоначалната функция и присвоения й контекст.

Следващото, коренно различно приложение на затварянията е защитата на данните (капсулиране). Разгледайте следната конструкция:

Код: (функция() (

})();

Очевидно вътре в затварянето имаме достъп до всички външни данни, но в същото време има и свои собствени. Благодарение на това можем да обградим части от кода с подобна конструкция, за да затворим локалните променливи, които са вътре, от достъп отвън. (Можете да видите един пример за използването му в изходния код на библиотеката jQuery, която обгражда целия си код със затваряне, така че да не излага променливи, от които се нуждае само извън нея).

Има обаче една клопка, свързана с тази употреба - вътре в затварянето значението на думата това извън него се губи. Решава се по следния начин:

Код: (функция() (
//по-високо това ще се запази
)).обадете се (това);

Нека да разгледаме друга техника от същата серия. Разработчиците на Yahoo UI framework го популяризираха навсякъде, наричайки го „Module Pattern“ и написвайки цяла статия за него в официалния блог.
Да предположим, че имаме обект (singleton), съдържащ някои методи и свойства:

Код: var MyModule = (
име: "Хабрахабр",
sayPreved: функция(име) (
alert("PREVED " + name.toUpperCase())
},
this.sayPreved(this.name);
}
}
MyModule.sayPrevedToHabrahabr();

С помощта на затваряне можем да направим частни методи и свойства, които не се използват извън обекта (т.е. достъпно само за него):

Код: var MyModule = (function() (
var name = "Habrahabr";
функция sayPreved() (
alert("PREVED " + name.toUpperCase());
}
връщане (
sayPrevedToHabrahabr: функция() (
sayPreved(име);
}
}
})();
MyModule.sayPrevedToHabrahabr(); //предупреждава "ПРЕДВИДЕН Habrahabr"

И накрая, искам да опиша често срещана грешка, която кара много хора в ступор, ако не знаят как работят затварянията.

Нека имаме масив от връзки и нашата задача е да се уверим, че когато щракнете върху всяка от тях, серийният й номер се показва като предупреждение.
Първото решение, което идва на ум изглежда така:

Код: за (променлива i = 0; i< links.length; i++) {
предупреждение(i);
}
}

Всъщност се оказва, че когато щракнете върху която и да е връзка, се показва същото число - стойността links.length. Защо се случва това? Поради затварянето, декларираната спомагателна променлива i продължава да съществува дори в момента, в който щракнем върху връзката. Тъй като по това време цикълът вече е преминал, i остава равен на броя на връзките - това е стойността, която виждаме при щракване.

Този проблем се решава по следния начин:

Код: за (променлива i = 0; i< links.length; i++) {
(функция(i) (
връзки[i].onclick = функция() (
предупреждение(i);
}
))(i);
}

Тук, с помощта на друго затваряне, ние "засенчваме" променливата i, създавайки нейно копие в нейния локален обхват на всяка стъпка от цикъла. Благодарение на това сега всичко работи по предназначение.

Това е всичко. Тази статия, разбира се, не претендира за изчерпателност, но се надявам, че все пак ще помогне на някой да го разбере.

PS
За да запазите между повикванията, е по-лесно да използвате func_name.attr като:

Код: функция countIt(reset) (
ако (нулиране ||! countIt.cnt) countIt.cnt = 0;
връщане countIt.cnt++;

В програмирането затварянето или „затваряне“ на английски е метод за имплементиране на обвързващо контекстно име в първокласен функционален език. Оперативно, това е запис, който съхранява функция заедно с нейната среда. Средата е картографиране на всяка безплатна функция към стойност или препратка по име, създадена от затваряне в Javascript. Той позволява достъп до уловени променливи чрез копия на стойности или препратки, дори когато се извиква извън обхвата.

Концепция за затваряне

Затварянията са разработени през 60-те години на миналия век за механична оценка на изрази в смятане и са внедрени през 1970 г. като характеристика на езика за програмиране PAL за поддръжка на първокласни функции с лексикален обхват. Peter Lundeen дефинира термина „затваряне“ през 1964 г. със среда и контрол, приложени към SECD машината с цел оценка на ламбда изрази, обвързани от лексикална среда, което води до затварянето им или затварянето им в Javascript.

Това обяснение беше въведено през 1975 г. като лексикално ограничен вариант на LISP и стана широко разпространено. Лексикалната среда е набор от реални променливи в програма. Състои се от вътрешна лексикална среда и препратки към външна среда, наречена нелокални променливи.

Лексикалните затваряния в Javascript са функции с неговата външна среда. Както в JavaScript, всички променливи имат препратка към тип. JS използва обвързване само чрез препратка - което е последователно в C++11 - и животът на нелокалните променливи, уловени от функция, се удължава до живота на функцията.

Затварянията в Javascript обикновено се появяват на езици с първокласни стойности. Такива езици позволяват функциите да се предават като аргументи. И също така връщане от извиквания на функции и обвързване с имена на променливи. Това е подобно на прости типове като низове и цели числа.

В този пример изразът ламбда (ламбда (книга) (>= (книга-продажба на книги) праг)) се появява във функцията за най-продавани книги. Когато се оценява ламбда израз, веригата създава затваряне, състоящо се от кода за ламбда израза и препратка към праговата променлива, която е свободната променлива в рамките на ламбда израза. След това затварянето се предава на филтърната функция, която я извиква многократно, за да определи кои книги трябва да бъдат добавени към списъка с резултати и кои трябва да бъдат отхвърлени.

Тъй като има затваряне в праговата стойност, последният може да го използва всеки път, когато филтърът го извика. Самата филтърна функция може да бъде дефинирана в напълно отделен файл. Ето същия пример, пренаписан в JS. Той демонстрира как затварянията работят под капака в Javascript.

Ключовата дума тук се използва вместо функцията за глобален филтър, но в противен случай структурата и ефектът на кода са същите. Една функция може да създаде затваряне и да го върне, защото след това оцелява след изпълнението на функцията, като променливите f и dx продължават да функционират след производната, дори ако изпълнението е напуснало техния обхват и те вече не са видими.

В езици без затваряне, животът на автоматична локална променлива е същият като изпълнението на рамката на стека, където променливата е декларирана. В езици със затваряне на Javascript и iife функции, променливите трябва да продължат да съществуват, докато всички съществуващи ключалки имат препратки към тях. Това най-често се прилага с помощта на някаква форма на събиране на боклук.

Предимството на затварянето е, че то запазва обхвата, "веригата на видимост" на външния или "родителския" контекст на изпълнение. Това поведение може да се използва по няколко начина и се превърна в полезен инструмент за предотвратяване на редица грешки в JavaScript. Един от най-често срещаните е проблемът с "примката".

Проблемът с цикъла възниква, когато потребителят създаде функция в цикъл и очаква текущата стойност на променливата да остане в тази нова функция, дори ако е променена в контекста на цикли, преди да извика новата функция. Затварянията, използвани по този начин, вече нямат референтна прозрачност и следователно вече не са чисти функции, но обикновено се използват в нечисти функционални езици като Scheme. За да разберете какво представляват затварянията в Javascript, трябва да разгледате техните случаи на използване. Всъщност на практика те имат много приложения:

  1. Те могат да се използват за определяне на управленски структури. Например, всички стандартни контролни структури на Smalltalk, включително разклонения (if/then/else) и цикли (while и for), се дефинират с помощта на обекти, чиито методи приемат затваряния. Потребителите могат също лесно да използват затваряния, за да дефинират контролни структури. На езици, които изпълняват целта, можете да създадете неговата многофункционална среда, която ви позволява да общувате частно и да променяте тази среда. Затварянията се използват за внедряване на обектни системи.
  2. Създавайте както частни, така и публични методи за променливи, като използвате шаблони на модули. Тъй като функциите за връщане наследяват обхвата на родителската функция, те са достъпни за всички променливи и аргументи в дадения контекст.
  3. Полезно е в ситуация, в която функция използва един и същ ресурс за всяко повикване, но също така създава самия ресурс за него. Това обстоятелство прави метода неефективен, което може да бъде елиминирано само чрез затваряне.

Според MDN (Mozilla Developer Network) „затварянията са функции с независими променливи, които „помнят“ средата на тяхното създаване.“ И обикновено, когато една функция излезе, нейните локални променливи вече не съществуват. За да разберете как работят затварянията в Javascript, можете да разгледате няколко механизма. Първият е формалната логика. Например, като използвате функцията logName, която приема едно име като параметър и го регистрира. След това създавам for цикъл за итерация през списъка с имена, задавам 1-ви таймаут и след това извиквам функцията logName, предавайки текущото име.

В първокласен език функциите могат да се манипулират по същия начин като други типове данни като int или string. Само този механизъм позволява на много хора да правят невероятни неща, като например присвояване на функция на променлива, която да бъде извикана по-късно, или предаването й като параметър на друга функция.

Този принцип се използва от много структури, както и от манипулатори на DOM събития. Първо те "слушат" събитието, след което присвояват функция за обратно извикване, която ще се извиква всеки път, когато събитието се задейства.

Анонимна функция е функция без име. Почти начинаещите програмисти се сблъскват с тях всеки ден, без да разбират играта с числата. Например, когато извършвате операция за добавяне, можете да преминете през променливите, например:

  • var x = 3;
  • y = 5;
  • var z = x + y.

Или ако нямате намерение да обработвате отново числата: var z = 3 + 5;

Това са анонимни номера. За анонимни функции можете да ги декларирате, когато се използват в движение - без да преминавате през променливата. Например вземете функцията do от по-рано:

( alert("Ceci est une fonction anonyme.");

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

В действителност това е същият механизъм, но от тази гледна точка ще ви позволи да видите как функцията е затворена отвътре. Както можете да видите, тъй като функциите са променливи като другите, няма причина те да не могат да бъдат дефинирани локално. В език с нулев ред, като C, C++ и Java, всички функции са дефинирани на едно и също ниво на видимост, в същия клас или на глобално ниво. От друга страна, в JavaScript локалната функция изчезва като други локални променливи, след като родителската функция приключи, така че не се вижда от други функции.

Всъщност е сложно, но JavaScript има начин да проследява видимостта на променливите и дори по два начина. Присвояването на глобална променлива в JavaScript има същия механизъм като в Java - сложни обекти, масиви, DOM елементи и други се предават по референция, така че в следния код:

var tab =; var tab2 = раздел.

Където tab и tab2 са две препратки към една и съща таблица, технически те са указатели, управлявани от събирача на отпадъци. Функциите също се предават по препратка. Променливата globalFn вече не е скрита. Редът позволява това да се случи, както се вижда от проблема със затварянето на Javascript.

Ето как можете да извлечете функция от локален контекст, ако функцията удовлетворява други локални променливи. Прост пример: автоматично нарастване, функция, която връща цяло число, което се увеличава с 1 при всяко извикване. По-конкретно, имаме нужда от функция inc, която се държи по следния начин:

// връщане на 0 inc();

// връщане на 1 inc();

// връщане 2 inc();

Със затваряне изглежда така:

функция makeInc() ( var x = 0; return function() ( return x++; ) ) var inc = makeInc();

В последния ред, в момента, в който се създава променливата функция inc, тя носи със себе си някои променливи, които са наоколо, в този случай x. Той създава някакъв невидим обект около функцията, която съдържа тази променлива. Този обект е функция за затваряне на Javascript. В този случай всяко копие на функцията ще има собствено затваряне:

var inc1 = makeInc();

var inc2 = makeInc();

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

За да се избегнат конфликти на имена на променливи, често се използват пространства от имена. В JavaScript пространствата от имена са обекти като всеки друг.

Естествено A.x и B.x не са една и съща променлива. Въпреки това, ако просто искате да стартирате скрипт, без да изисквате променливи да бъдат записани за други, можете да използвате анонимна функция като затваряне. Това дава малко странен синтаксис. Докато двата реда код в средата са доста често срещани, от друга страна, функцията, която е наоколо, се изпълнява в движение. Обърнете внимание на скобите () в края. И за да можете да направите затваряне, самата анонимна функция трябва да бъде оградена със скоби.

Тази анонимна функция използва локална променлива, параграф. Това е чудесен начин за предотвратяване на конфликти с имена или неудобства, но също и срещу XSS атаки, потребителските променливи са защитени, никой не може да ги промени, за да повлияе на поведението на скрипта.

Има опция: (function() (// ...)());

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

Програмиране на Javascript в цикли

Когато потребител прави големи количества програмиране на Javascript, става трудно да се избегнат цикли. Това подлудява някои хора, карайки ги да вярват, че всяка реализация на Javascript е сериозно погрешна. Ако разработчикът вече има цикъл, който не иска да преобразува, за да използва функцията итератор, всичко, което трябва да направи, е затваряне, в което той дефинира нови променливи. Те записват текущата стойност на променливите, които се променят при всяка итерация. Номерът за улавяне на променливи е, че външното затваряне се изпълнява незабавно по време на текущата итерация на цикъла. Можете да използвате един от тези два примерни подхода

Сега има друго опростено решение на този проблем, тъй като ключовата дума let се поддържа както във Firefox, така и в Chrome. Това е ключова дума вместо блок променлива var. Let работи магически, защото се декларира нова променлива j, като стойността на i се фиксира чрез затваряне вътре в цикъла. Трябва обаче да се има предвид, че той не продължава да съществува след края на една итерация на цикъла, тъй като е локален.

Цикл и функция

Цикъл for в JavaScript не е представен по същия начин като цикъл for в C или Java. Всъщност е по-скоро като PHP. Най-важното нещо, което трябва да знаете за циклите в JS е, че те не създават обхват. JS няма сферичен блок, а само функция за обем. Това свойство може да се види в следния фрагмент:

функция foo() (var bar = 1;

for(var i = 0; i< 42; i++) {var baz = i;} /* more code */}

Ясно е, че лентата е налична в цялата функция. Преди първата итерация на цикъла baz ще бъде недефиниран. След цикъла ще има стойност 41 (и аз ще бъда 42). По този начин всяка променлива, декларирана навсякъде във функция, ще бъде достъпна навсякъде във функцията и ще има стойност само след като й бъде присвоена.

Порти и агрегация

Затварянето не е нищо повече от функции, вътре в други функции и преминаване към някакъв друг контекст. Те се наричат ​​затваряния, защото се затварят чрез локални променливи, тоест те са достъпни за други функции на сферата. Например време x, посочено като параметър foo и var bar = foo(2)(), ще върне 84.

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

// elements е масив от 3 DOM елемента var values ​​​​= ["foo", "bar", "baz"];

аз< l; i++) {var data = values[i];

елементи[i].onclick = функция() (предупреждение(данни);

Стойността, която ще използват за предупреждение при щракване, ще бъде една и съща за всички, а именно baz. Докато се извика манипулаторът на събитие, for вече е завършен. JS няма блоков обхват, т.е. всички манипулатори използват препратка към една и съща променлива с данни. След цикъла тази стойност ще бъде стойности. Всяка декларация на променлива създава едно място в паметта за съхранение. Тъй като тези данни се променят отново и отново, местоположението в паметта остава непроменено.

Всеки манипулатор на събития има достъп до едно и също място в паметта. Единственото решение е да се въведе друг обхват, който "улавя" текущата стойност на данните. JS има само функционален обхват. Затова се въвежда друга функция. Пример:

функция createEventHandler(x) (връщане на функция() (предупреждение(x);

for(var i = 0, l = elements.length;

аз< l; i++) {var data = values[i];

елементи[i].onclick = createEventHandler(данни);

Това работи, защото стойността на данните ще се съхранява в локалния обхват на createEventHandler и тази функция се изпълнява при всяка итерация. Това може да бъде написано по-кратко, като се използват незабавно изпълними функции:

for(var i = 0, l = elements.length;

аз< l; i++) {var data = values[i];

елементи[i].onclick = (функция(x) (функция() (предупреждение(x);

Практически пример за затваряне в Javascript

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

функция работа (име)(

функция за връщане (тема) (

console.log(Какво е $(тема) в $(име));

работа ("Javascript") ("Затваряне");

Първо се извиква функцията и се предава аргументът име. Сега тази функция на лексикона също връща функция, която също приема аргумент на тема. Тази функция регистрира изхода и изходът има достъп до променлива.

Обхватът на функциите Insider не е ограничен до тази функция, така че концепцията се нарича Closure, защото има достъп до дадения обхват на външния параметър. Върнатата функция има достъп до външния лексикален обхват или контексти. Когато разработчикът извика функция, която също я връща, първите извикани функционални променливи са винаги достъпни за вътрешната функция. По-долу е даден пример със следния код.

Пример за вътрешна функция

Можете да научите повече за затварянията в Javascript във втория пример. Сега това време за изпълнение е унищожено, но името на параметъра все още съществува. Създава се нова вътрешна функционална среда, която е анонимна функция. Тя има достъп до зоната на външната лексикална среда.

По този начин променливата на външната среда все още съществува, така че анонимната функция, която има достъп до променливата за име, отпечатва на конзолата, например „Какво е затваряне в Javascript“. Вътрешна анонимна функция //main.js

функция factory())( var products =;

i++)( products.push(function () ( console.log(i);

) връщане на продукти;

) var soap = factory();

Резултатът от този пример е съвсем незначителен и е равен на 2.

Когато soap() се нарича външна контекстна променлива, тя винаги е 2, защото в цикъла условието е невярно при i<2, поэтому при этом значение i равно 2, а во время вызова нужно напечатать значение в консоль так, она всегда пишет 2. То же самое для мыла - soap ().

Създаване на функции в движение

Можете да създадете фабрика за функции - functionFactory, която изпълнява потребителски задачи. Получената функция от фабриката за функции ще бъде затваряне, което помни средата за създаване.

var functionFactory = функция(num1) (връщане на функция(num2) (връщане num1 * num2;

Горното ви позволява да подадете един номер на функцияFactory. functionFactory след това връща Closure, който съхранява стойността на num1. Получената функция умножава оригиналното num1 по стойността на num2, която се предава при извикване.

var mult5 = functionFactory(5);

var mult10 = functionFactory(10);

Горното просто създава функциите mult5 и mult10. Сега можете да посочите всяка от тези функции, като подадете ново число, което трябва да бъде умножено по 5 или 10. Сега можете да видите резултата.

Затварянето е една от най-мощните функции на javascript, но не може да се използва правилно без разбиране на концепцията. Те са относително лесни за създаване случайно, поради което затварянето на Javascript е опасно. Създаването им има потенциално вредни последици, особено в някои сравнително често срещани среди на уеб браузъри. За да избегнете случайно срещане на недостатъци и да се възползвате от предимствата, които предлагат, е необходимо да разберете техния механизъм.

Повече за затварянията в JavaScript

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

Наистина, късите съединения се създават много лесно, дори по невнимание, и могат да доведат до проблеми. Но всъщност те са много удобни, просто трябва да разберете какво наистина се случва.

Просто описание

Просто казано, затварянията са вътрешни функции. В крайна сметка javascript ви позволява да създавате функции, докато скриптът се изпълнява. И тези функции имат достъп до променливите на външната функция.

Този пример създава вътрешна функция func, от която са достъпни както локалните променливи, така и външните функционални променливи:

функция outer() ( var outerVar; var func = function () ( var innerVar ... x = innerVar + outerVar ) return func )

Когато външната функция завърши изпълнението, вътрешната функция func остава жива и може да се изпълнява другаде в кода.

Оказва се, че при стартиране на func се използва променливата на вече изпълнената функция outer, т.е. от самия факт на съществуването си func затварявърху себе си променливите на външната функция (или по-скоро на всички външни функции).

Най-често използваните затваряния са за присвояване на функции за обработка на събития:

Тук динамично създаденият манипулатор на събитие използва targetId от външната функция за достъп до елемента.

Ако искате да отидете по-дълбоко и да разберете по-дълго...

В действителност това, което се случва в интерпретатора на Javascript, е много по-сложно и съдържа много повече подробности, отколкото е описано тук...

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

[]

Всяко изпълнение на функция съхранява всички променливи в специален обект с кодово име [], който не може да бъде достъпен изрично, но съществува.

Всяко извикване на var ... просто създава ново свойство на този обект и всяко споменаване на променлива първо се търси в свойствата на този обект.

Това е вътрешната структура на "обхвата" - обикновен обект. Всички промени в локалните променливи са промени в свойствата на този неявен обект.

Обикновено, след като дадена функция приключи изпълнението, нейният обхват [], тоест целият набор от локални променливи, се унищожава.

Общият поток на изпълнение изглежда така:

Между другото, за код извън функцията (и глобалните променливи като цяло), ролята на обекта контейнер [] се изпълнява от обекта прозорец.

Обхват на вложена функция

Когато една функция се създава в друга, за нея се създава препратка към обект с локални променливи [] на външната функция.

Поради това променливите на външната функция могат да бъдат получени от вътрешната функция - чрез връзка към нейния [] . Първо гледаме в себе си, след това във външния [] - и така по веригата до самия обект прозорец.

Затварянето е, когато обектът на локалните променливи [] на външната функция се поддържа активен, след като тя е завършила.

Вътрешната функция може да получи достъп до него по всяко време и да получи външната функционална променлива.

Например, нека разгледаме функцията, която инсталира манипулатори на събития:

функция addHideHandler(sourceId, targetId) ( // създава се обект [] със свойства sourceId, targetId // запис в [] свойството sourceNode var sourceNode = document.getElementById(sourceId) // запис в [] свойството манипулатор var handler = function () ( var targetNode = document.getElementById(targetId) targetNode.style.display = 'none' ) sourceNode.onclick = манипулатор // функцията завърши изпълнението // (***) и ето най-интересното! }

Когато функцията се стартира, всичко се случва стандартно:

  1. създаде []
  2. там са записани локални променливи
  3. вътрешната функция получава препратка към []

Но в самия край - вътрешната функция е присвоена на sourceNode.onclick. Външната функция е приключила работата си, но вътрешната може да се задейства по-късно.

Интерпретаторът на javascript не анализира дали вътрешната функция ще се нуждае от променливи от външната и какви променливи може да са необходими.

Вместо това, той просто оставя цялата [] външна функция жива.

Така че, когато вътрешната функция стартира, ако внезапно не намери променлива в своя [] - тя може да се обърне към външната функция [] и да я намери там.

Ако външна функция е създадена вътре в друга (още по-външна) функция, тогава към веригата се добавя друг консервиран [] и така нататък - до глобалната област на прозореца.

Пример за разбиране

В този пример външната функция makeShout() създава вътрешната функция shout().

function makeShout() ( var phrase = "Хей!" var shout = function () ( alert (phrase) ) phrase = "Готово!" return shout ) shout = makeShout() // какво ще покаже? вик()

Функцията shout(), като вътрешна функция, има достъп до променливата на фразата. Каква стойност ще изведе - първата или втората?

А ето и подробно описание на случващото се в недрата на javascript:

  1. Вътре makeShout()
    1. създаде []
    2. В [] е написано: phrase="Preved!"
    3. В [] е написано: shout=..function..
    4. shout получава препратка към [] външна функция
    5. [].phrase се променя на новата стойност "Готово!"
  2. Когато работите, извикайте ()
    1. Създава свой собствен обект []
    2. Търси се фраза на [] - не е намерена
    3. Търсене на фраза в [] външна функция - намерена е стойността "Готово!"
    4. предупреждение ("Готово!")

Това е, Вътрешната функция получава най-новата стойност на външните променливи.

Пример за неправилна употреба

Функцията addEvents взема масив от divs и присвоява на всеки от тях да показва своя номер при onclick.

От въпроса "Защо това не работи?" хората обикновено започват да изучават затваряния.

функция addEvents(divs) ( for (var i=0; i

За тестовия пример, нека направим 10 многоцветни номерирани div с различни цветове:

функция makeDivs(parentId) ( for (var i=0 ;i<10 ;i++) { var j = 9 -i var div = document.createElement ("div" ) div.style .backgroundColor = "#" +i+i+j+j+j+i div.className ="closure-div" div.style .color = "#" +j+j+i+i+i+j document.getElementById (parentId) .appendChild (div) } }

Бутонът по-долу ще създаде 10 div и ще извика addEvents върху тях

Ако щракнете върху div, всички те показват едно и също предупреждение.

Този проблем възникна поради факта, че всички функции div[i].onclick получават стойността i от една на всички [] външна функция. И тази стойност ([].i ) по време на активиране на манипулатора onclick е 10 (цикълът приключи веднага щом i==10).

За да бъде всичко в ред, в такива случаи се използва специална техника - подчертаване [] . Следната функция работи правилно. Всичко е същото с изключение на div.onclick.

функция addEvents2(divs) ( for (var i=0; i

Сега всичко трябва да е наред - всеки div дава сигнал за своя номер.

За да присвоите div.onclick, се изпълнява временна функция function(x) (..), като се взема аргумент x и се връща манипулатор, който взема x от [] на тази временна функция.

Function(x) (..) се използва за създаване на функция, а след това (i) се използва за изпълнението й с аргумент i.

Като цяло javascript е много удобен език в този смисъл. Позволява всякакви конструкции, например:

предупреждение ( функция a() ( return [ 5 ] ) () [ 0 ] ) // => ще отпечата 5

Временната функция function(x) (..) спира да работи незабавно, оставяйки в своя [] правилната стойност на x, равна на променливата на текущия цикъл i.

Когато манипулаторът е активиран, предупреждението ще вземе правилната стойност x от [] най-близката външна функция.

На теория, тези примери трябва да са достатъчни, за да разберете и практически да използвате затварянията.

В JavaScript функциите могат да бъдат описани не само една след друга, но и една в друга. Когато имате една функция в друга, вътрешната функция има достъп до променливите на външната функция.

Функция external(x) ( var tmp = 3; function internal(y) ( alert(x + y + (++tmp)); // ще покаже 16 ) internal(10); ) external(2);

Този код винаги произвежда 16, защото вътрешната функция вижда x, което е променлива във външната функция. В този случай аргументът на функцията. Също така, internal() може да види tmp от external().

Това се нарича затваряне. По-точно, външната функция се нарича затваряне, а всичко вътре в нея се нарича среда на затваряне.

Понякога казват, че затварянето е функция, която връща функция, това е неправилно; за да се нарече функция затваряне, достатъчно е вътрешната функция да има достъп до променлива извън нейния обхват.

Функция foo(x) ( var tmp = 3; функция за връщане (y) ( alert(x + y + (++tmp)); // също ще предупреди 16 ) ) var bar = foo(2); // лентата вече е затваряне. лента (10);

Горната функция също ще върне 16, тъй като bar, дори след като foo е завършил, продължава да има достъп до x и tmp, въпреки че самият bar не е в обхвата, в който са били декларирани.

Въпреки това, тъй като променливата tmp все още е вътре в лентата за затваряне, тя продължава да се увеличава при всяко извикване на лента.

Ето прост пример за затваряне:

Var a = 10; функция test() ( console.log(a); // изход 10 console.log(b); // изход 6) var b = 6; тест();

Когато стартирате функция в JavaScript, за нея се създава среда, тоест списък с всички видими за нея променливи, не само аргументите и променливите, декларирани в нея, но и извън нея, в този пример това са „a ” и „б”.

Можете да създадете повече от едно затваряне в една и съща среда, като ги върнете като масив, обект или ги свържете към глобални променливи. В този случай всички те ще работят с едно и също нещостойността на x или tmp без създаване на отделни копия.

Тъй като в нашия пример x е число, неговата стойност е копиранна foo като свой аргумент x.

От друга страна, JavaScript винаги използва препратки, когато предава обекти. Ако извикате foo с обект като аргумент, върнатото затваряне ще върне препратка към оригиналния обект!

Функция foo(x) ( var tmp = 3; функция за връщане (y) ( alert(x + y + tmp); x.memb = x.memb ? x.memb + 1: 1; alert(x.memb); ) ) var age = new Number(2); var bar = foo(възраст); // лентата вече е възраст за затваряне. лента (10);

Както бихте очаквали, всяко извикване на bar(10) увеличава x.memb. Това, което може да не очаквате е, че x продължава да се отнася за същия обект като age! След две обаждания до бар, age.memb ще бъде 2! Между другото, така възникват течове на памет в HTML обекти.

Последна актуализация: 30.03.2018

Затваряния

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

Технически затварянето има три компонента:

    външна функция, която дефинира някакъв обхват и в която са дефинирани някои променливи - лексикалната среда

    променливи (лексикална среда), които са дефинирани във външната функция

    вложена функция, която използва тези променливи

функция outer())( // външна функция var n; // някаква променлива връща inner())( // вложена функция // действия с променлива n ) )

Нека да разгледаме затварянията, използвайки прост пример:

Функция outer())( let x = 5; функция inner())( x++; console.log(x); ); return inner; ) let fn = outer(); // fn = inner, тъй като външната функция връща вътрешната функция // извикване на вътрешната функция inner fn(); // 6 fn(); // 7 fn(); // 8

Тук външната функция указва обхвата, в който са дефинирани вътрешната функция inner и променливата x. Променливата x представлява лексикалната среда за вътрешната функция. В самата вътрешна функция ние увеличаваме променливата x и извеждаме нейната стойност на конзолата. Накрая външната функция връща вътрешната функция.

Нека fn = външен();

Тъй като външната функция връща вътрешната функция, променливата fn ще съхранява препратка към вътрешната функция. В същото време тази функция запомни своята среда - тоест външната променлива x.

Fn(); // 6 fn(); // 7 fn(); // 8

Тоест, въпреки факта, че променливата x е дефинирана извън вътрешната функция, тази функция е запомнила своята среда и може да я използва, въпреки факта, че се извиква извън външната функция, в която е дефинирана. Това е същността на затварянията.

Нека да разгледаме друг пример:

Функция multiply(n)( var x = n; return function(m)( return x * m;); ) var fn1 = multiply(5); var result1 = fn1(6); // 30 console.log(result1); // 30 var fn2= multiply(4); var result2 = fn2(6); // 24 console.log(result2); // 24

Така че тук извикването на функцията multiply() води до извикване на друга вътрешна функция. Вътрешната функция:

Функция(m)( return x * m;);

запомня средата, в която е създаден, по-специално стойността на променливата x.

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

Тоест, fn1 е затваряне, което съдържа както вътрешна функция function(m)( return x * m;), така и променлива x, която е съществувала по време на създаването на затварянето.

Когато създадете две затваряния: fn1 и fn2, всяко от тези затваряния създава своя собствена среда.

Важно е да не се бъркате в параметрите. Когато дефинирате затваряне:

Var fn1 = умножение(5);

Числото 5 се предава като n параметър на функцията за умножение.

При извикване на вътрешна функция:

Var result1 = fn1(6);

Числото 6 се предава за параметър m на вътрешната функция function(m)( return x * m;); .

Можем също да използваме друга опция за извикване на затварянето:

Функция multiply(n)( var x = n; return function(m)( return x * m;); ) var result = multiply(5)(6); // 30 console.log(резултат);

Функции за самоизвикване

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

(функция())( console.log("Здравей свят"); )()); (функция (n)( var result = 1; for(var i=1; i<=n; i++) result *=i; console.log("Факториал числа " + n + " равен " + result); }(4));

Такива функции са оградени в скоби и след като функцията е дефинирана, параметрите се предават в скоби.