Mi a JavaScript bezárása? Kérem egyszerű angol nyelven.

A JavaScript minden funkciója bezárult. És ez a JavaScript nyelv egyik legmenőbb tulajdonsága. Mivel bezárások nélkül nehéz lenne olyan közös struktúrákat megvalósítani, mint a visszahívások vagy az eseménykezelők.

Zárást hoz létre, amikor meghatároz egy függvényt. Amikor a funkciókat végrehajtja, a bezárásaik lehetővé teszik számukra, hogy hozzáférjenek a hatókörükbe tartozó adatokhoz.

Ez olyan, mint amikor egy autó gyártják (meghatározott) jön egy pár funkciók, mint start, accelerate, decelerate. Ezeket az autófunkciókat a sofőr hajtja végre minden alkalommal, amikor az autót működtetik. Ezeknek a funkcióknak a bezárása magával az autóval van meghatározva, és bezárul az üzemeltetéshez szükséges változók fölött .

Szűkítsük ezt az analógiát a acceleratefüggvényre. A funkció meghatározása akkor következik be, amikor az autót gyártják:

function accelerate(force) { // Is the car started? // Do we have fuel? // Are we in traction control mode? // Many other checks... // If all good, burn more fuel depending on // the force variable (how hard we’re pressing the gas pedal) }

Minden alkalommal, amikor a vezető lenyomja a gázpedált, ez a funkció végrehajtásra kerül. Vegye figyelembe, hogy ennek a funkciónak a működéséhez sok változóra van-e szüksége, beleértve a saját forceváltozóját is. De ami még ennél is fontosabb, a hatókörén kívüli változókra van szüksége, amelyeket más autófunkciók vezérelnek. Itt acceleratejön jól a funkció bezárása (amelyet magával az autóval kapunk).

A acceleratefunkció bezárása ígéretet tett acceleratemagának a funkciónak:

Ok accelerate, amikor végrehajtják, elérheti a forceváltozóját, elérheti a isCarStartedváltozót, elérheti a fuelLevelváltozót és a isTractionControlOnváltozót is. Vezérelheti azt a currentFuelSupplyváltozót is, amelyet a motornak küldünk.

Ne feledje, hogy a bezárás nem adott fix értékeket a acceleratefüggvénynek ezekhez a változókhoz, hanem engedélyt adott az értékek elérésére a gyorsító függvény végrehajtásakor.

A bezárások szorosan kapcsolódnak a funkció hatóköréhez, így ezeknek a hatóköröknek a megértése segít megérteni a bezárásokat. Röviden, a hatókörökkel kapcsolatos legfontosabb megértés az, hogy egy függvény végrehajtásakor létrejön egy privát függvény hatókör, amelyet felhasználnak a függvény végrehajtásának folyamatához.

Ezután ezek a funkciókörök beágyazódnak, amikor a funkciókat a függvények belsejéből hajtja végre (amit folyamatosan fog csinálni).

A bezárás akkor jön létre, amikor meghatároz egy függvényt, nem pedig akkor, amikor végrehajtja azt. Ezután minden alkalommal, amikor végrehajtja ezt a funkciót, a már meghatározott bezárása hozzáférést biztosít a körülötte elérhető összes funkciókörhöz.

Bizonyos szempontból elképzelhető, hogy a hatókörök ideiglenesek (ez alól az egyetlen kivétel a globális hatókör), míg maguk a bezárások tartósnak tekinthetők.

Ahhoz, hogy valóban megértse a bezárásokat és a JavaScriptben betöltött szerepüket, először meg kell értenie néhány további egyszerű fogalmat a JavaScript funkcióiról és azok hatóköréről.

Mielőtt belekezdenénk, vegye figyelembe, hogy ehhez létrehoztam egy interaktív laboratóriumot is, amelyet itt átdolgozhat.

1 - A funkciókat értékreferencia hozzárendeli

Ha egy függvényt egy ilyen változóba tesz:

function sayHello() { console.log("hello"); }; var func = sayHello;

A változóhoz funchivatkozást rendel a függvényhez sayHello, nem másolatot. Itt funcegyszerűen álneve a sayHello. Bármit, amit az álnéven tesz, ténylegesen az eredeti függvényben fogja végrehajtani. Például:

func.answer = 42; console.log(sayHello.answer); // prints 42

Az ingatlan answerközvetlenül be volt kapcsolvafuncés olvasni sayHello, ami működik.

sayHelloVégrehajthatja az funcálnév futtatásával is :

func() // prints "hello"

2 - A hatókörök élettartama van

Ha meghív egy függvényt, akkor a függvény végrehajtása során létrehoz egy hatókört. Akkor ez a hatókör elmúlik.

Amikor másodszor hívja meg a függvényt, a második végrehajtás során új, más hatókört hoz létre. Akkor ez a második hatókör is elmúlik.

function printA() { console.log(answer); var answer = 1; }; printA(); // this creates a scope which gets discarded right after printA(); // this creates a new different scope which also gets discarded right after;

Ez a két hatókör, amelyet a fenti példában hoztak létre, különböznek egymástól. Az answeritt szereplő változót egyáltalán nem osztják meg közöttük.

Minden funkció hatókörének élettartama van. Létrehozzák őket, és azonnal eldobják őket. Az egyetlen kivétel ez alól a globális hatókör, amely nem múlik el, amíg az alkalmazás fut.

3 - A zárások több hatókörre terjednek ki

Ha meghatároz egy függvényt, létrejön egy lezárás

A hatókörökkel ellentétben a bezárások egy funkció definiálásakor jönnek létre , nem pedig akkor, amikor azt végrehajtja. A bezárások sem múlnak el a funkció végrehajtása után.

Az adatokhoz bezárva még egy funkció definiálása és a végrehajtás után is hozzáférhet.

A bezárások felölelik mindazt, amit a meghatározott funkció elérhet. Ez a definiált függvény hatókörét, valamint a globális hatókör és a definiált függvény hatóköre, valamint maga a globális hatókör közötti beágyazott hatóköröket jelenti.

var G = 'G'; // Define a function and create a closure function functionA() { var A = 'A' // Define a function and create a closure function functionB() { var B = 'B' console.log(A, B, G); } functionB(); // prints A, B, G // functionB closure does not get discarded A = 42; functionB(); // prints 42, B, G } functionA();

Amikor itt meghatározzuk functionB, létrehozott bezárása lehetővé teszi számunkra, hogy hozzáférjünk a functionBplusz functionAés a globális hatókör hatályához.

Minden alkalommal, amikor végre functionB, akkor érheti el változók B, AésGa korábban létrehozott bezárás révén. Ez a lezárás azonban nem ad nekünk másolatot ezekről a változókról, hanem inkább hivatkozást ad rájuk. Tehát ha például a változó értéke Avalamikor megváltozik a bezárás functionBlétrehozása functionBután , amikor ezt végrehajtjuk , akkor az új értéket látjuk, nem a régit. A második felhívás a functionBnyomtatásra42, B, Gmert a változó értéke A42-re változott, és a lezárás hivatkozást adott nekünk A, nem pedig egy példányt.

Ne keverje össze a lezárásokat a hatókörökkel

Gyakran előfordul, hogy a lezárásokat összekeverik a hatókörökkel, ezért ügyeljünk arra, hogy ezt ne tegyük.

// scope: global var a = 1; void function one() { // scope: one // closure: [one, global] var b = 2; void function two() { // scope: two // closure: [two, one, global] var c = 3; void function three() { // scope: three // closure: [three, two, one, global] var d = 4; console.log(a + b + c + d); // prints 10 }(); }(); }();

A fenti egyszerű példában három funkcióval rendelkezünk, és mindegyikük definiálódik és azonnal meghívásra kerül, így mindannyian hatóköröket és bezárásokat hoznak létre.

The scope of function one() is its body. Its closure gives us access to both its scope and the global scope.

The scope of function two() is its body. Its closure gives us access to its scope plus the scope of function one()plus the global scope

And similarly, the closure of function three() gives us access to all scopes in the example. This is why we were able to access all variables in function three().

But the relation between scopes and closures is not always simple like this. Things become different when the defining and invoking of functions happen in different scopes. Let me explain that with an example:

var v = 1; var f1 = function () { console.log(v); } var f2 = function() { var v = 2; f1(); // Will this print 1 or 2? }; f2();

What do you think the above example will print? The code is simple, f1() prints the value of v, which is 1 on the global scope, but we execute f1() inside of f2(), which has a different v that’s equal to 2. Then we execute f2().

Will this code print 1 or 2?

If you’re tempted to say 2, you’ll be surprised. This code will actually print 1. The reason is, scopes and closures are different. The console.log line will use the closure of f1(), which is created when we define f1(), which means the closure of f1() gives us access to only the scope of f1() plus the global scope. The scope where we execute f1() does not affect that closure. In fact, the closure of f1() will not give us access to the scope of f2() at all. If you remove the global v variable and execute this code, you’ll get a reference error:

var f1 = function () { console.log(v); } var f2 = function() { var v = 2; f1(); // ReferenceError: v is not defined }; f2();

This is very important to understand and remember.

4 — Closures have read and write access

Since closures give us references to variables in scopes, the access that they give us means both read and write, not just read.

Take a look at this example:

function outer() { let a = 42; function inner() { a = 43; } inner(); console.log(a); } outer();

The inner() function here, when defined, creates a closure that gives us access to the variable a. We can read and modify that variable, and if we do modify it, we will be modifying the actual a variable in the outer() scope.

This code will print 43 because we used the inner() function closure to modify the outer() function variable.

This is actually why we can change global variables everywhere. All closures give us both read and write access to all global variables.

5 — Closures can share scopes

Since closures give us access to nested scopes at the time we define functions, when we define multiple functions in the same scope, that scope is shared among all created closures, and of course, because of this, the global scope is always shared among all closures.

function parent() { let a = 10; function double() { a = a+a; console.log(a); }; function square() { a = a*a; console.log(a); } return { double, square } } let { double, square } = parent(); double(); // prints 20 square(); // prints 400 double(); // prints 800

In the example above, we have a parent() function with variable a set to 10. We define two functions in this parent() function’s scope, double() and square(). The closures created for double() and square() both share the scope of the parent() function. Since both double() and square() change the value of a, when we execute the last 3 lines, we double a (making a = 20), then square that doubled value (making a = 400), then double that squared value (making a = 800).

One final test

Let’s now check your understanding of closures so far. Before you execute the following code, try to guess what it will print:

let a = 1; const function1 = function() { console.log(a); a = 2 } a = 3; const function2 = function() { console.log(a); } function1(); function2();

I hope you got that right and I hope these simple concepts will help you to truly understand the significant role function closures play in JavaScript.

Thanks for reading.

Learning React or Node? Checkout my books:

  • Learn React.js by Building Games
  • Node.js Beyond the Basics