Dykk ned i rækkevidde kæder og lukninger

Hvordan Scope kæde og lukninger fungerer under emhætten med eksempler.

Forståelse af omfang og lukninger i JavaScript

For at grave dybt og få de oplysninger, du har brug for, skal du tænke som en journalist. Stil de seks hovedspørgsmål: hvem, hvad, hvorfor, hvor, hvornår og hvordan. Hvis du kan besvare alle disse om et bestemt emne, har du fået essensen af ​​det, du har brug for at vide.

Før vi kommer til lukninger, skal vi have en forståelse af omfanget.

For det første, hvis du ved hvad [[scope]] (omfang med dobbelt parentes) er, så er denne artikel ikke noget for dig. Du har mere avanceret viden og kan komme videre.

Hvad for en…

Hvad er omfang og hvorfor betyder det noget?

Omfang er kontekstmiljøet (også kendt som leksikalt miljø), der oprettes, når en funktion skrives. Denne sammenhæng definerer, hvilke andre data den har adgang til.

Sagt på en anden måde, omfanget handler om adgang. Har funktionen evnen til at slå op til en variabel til udførelse eller manipulation, hvilke variabler er synlige?

Der er to typer anvendelsesområde: lokalt og globalt. Omfangsopløsning eller at finde ud af, hvilke variabler der hører hjemme, starter i den inderste sammenhæng og fortsætter udad, indtil identifikatoren findes. Lad os starte i det lille ...

var firstNum = 1;
function number() { var secondNum = 2; return firstNum + secondNum;}
number();

Hvornår, hvorfor og hvordan ... eksekveringskontekst

Når funktionen påberåbes, danner den en ny udførelseskontekst. Hvad er en eksekveringskontekst? Ligesom vi har to typer anvendelsesområde, har vi to typer udførelseskontekst. De er en global udførelseskontekst og en funktionskørselskontekst.

Den globale kontekst kører altid. I tilfælde af et browsermiljø stopper det kun, når browseren er lukket. Når vi kalder en funktion, placerer vi funktionens eksekveringskontekst oven på den globale eksekveringskontekst. Derfor terminologien vi stabler dem.

JavaScript er et enkelt trådsprog, hvilket betyder, at det kun kan gøre en ting ad gangen. Når vi kalder en funktion, stoppes den foregående udførelseskontekst. Den kaldte funktion er øverst, og den udføres derefter. Når det er færdigt, poppes det fra stakken, og derefter genoptages den ældre eksekveringskontekst. Denne 'stak' af udførelse er det, der holder styr på positionen for udførelse i vores ansøgning. Det er også vigtigt at slå identifikatorer op.

Så nu har vi dannet en eksekveringskontekst, hvad er det næste?

Hver udførelseskontekst har et tilknyttet variabelt objekt

For det første dannes et aktiveringsobjekt (ikke tilgængeligt med kode, men fungerer endnu i baggrunden). Det er forbundet med denne eksekveringskontekst. Dette objekt indeholder alle deklarerede variabler , funktioner og parametrevideregivet inden for denne sammenhæng (dens omfang eller tilgængelighedsområde).

Parametre til en funktion er implicit defineret. De er "lokale" i forhold til den funktion. Disse deklarerede variabler “hejses”, føres til toppen af ​​det omfang, de hører til.

Før jeg går videre for at undgå forvirring - i den globale udførelseskontekst oprettes et variabelt objekt , og hvis det er en funktion, er det et aktiveringsobjekt . De er stort set identiske.

Når denne funktion nu påberåbes, oprettes der en "omfangskæde" af disse objekter. Hvorfor? Omfangskæden er en måde at linke eller give en systematisk adgang til alle variabler og andre funktioner, som den aktuelle eksekveringskontekst (funktion i dette tilfælde) har adgang til. [[Scope]] er den skjulte mekanisme, der forbinder disse variable objekter til identifikationsopslag. Denne skjulte [[Scope]] er en egenskab ved funktionen, oprettet ved erklæring, ikke påkaldelse.

Aktivering Objekt øverst på omfangskædetoget, hvis det er en funktion . Dette aktiveringsobjekt har sine egne deklarerede variabler, argumenter og dette.

Dernæst på omfangskæden er det næste objekt fra den indeholdende kontekst. Hvis det er en global variabel, er det et variabelt objekt. Hvis det er en funktion, er det et aktiveringsobjekt . Dette sker, indtil vi når den globale kontekst. Derfor kan du se, at vi starter fra den inderste sammenhæng til den yderste, tænker russiske redende dukker.

Hvad er forskellen mellem en variabel, der er deklareret, og en, der er sort deklareret? Hvis identifikatoren er forud for en var, let eller const, erklæres den eksplicit, og der tildeles hukommelsesplads til den variabels brug. Hvis identifikatoren ikke eksplicit erklæres, erklæres den implicit i det globale omfang, som vi snart vil undersøge. Med henblik på denne artikel holder jeg fast med var, ingen særlig grund.

I know, the above was a little technical, and to be honest as I wrote this, I only learned of the Variable and Activation objects myself. Now that you had the deep dive explanation, here’s a high angle description…

The scope chain is similar to the prototype chain. If a variable or property is not found, it continues up the chain until it is either found or a error is thrown. The function creates a hidden [[scope]] property. This property links innermost scopes to outermost scopes. In this case, number’s scope chain is linked to the global window object (the containing context that holds function number). This is what allows the engine to look outside of function number to find firstNum and secondNum.

For example, let’s take the same function number and change one thing:

// global scope - includes firstNum, secondNum, and the// function number
var firstNum = 1;
function number() { // local scope for number - only thirdNum is local to number() // because it was explicitly declared. secondNum is implicitly declared in the // the global scope.
secondNum = 2; var thirdNum = 3; return firstNum + secondNum; }// what do we have access to in the global scope?number(); // 3firstNum; // 1secondNum; // 2thirdNum; // Reference Error: thirdNum is not defined

When speaking of global scope, variable declarations, non-nested function declarations, and function expressions (still considered a variable definition) are considered in the scope of the global window object in the browser. So as we see above, the window object has a properties firstNum, secondNum, and number added to it. If we proceed along the scope chain looking for it, we keep looking until we reach the global context’s variable object. If it’s not in there, then we get the Reference Error.

In a new tab, type "about:blank" in the search bar. A blank page will open and hit cmd-option-i to open dev tools.
Type the code above and remember, shift-enter for a new line!
Now type "window" and explore all the properties on the window object.
Look closely and you will see the properties firstNum, secondNum, and number are all available on the window object.

When we try to access thirdNum outside of where it was declared, we get a Reference Error. The engine that compiles the code failed to find an identifier in the window global scope object.

ThirdNum is only available inside of the function where it was declared. It is encapsulated or private to function number

The question you may have is “Does the global scope has access to everything inside of number?” Again, scope only works from the inside out, the innermost context, local, to the outermost context, global.

Starting with local scope, we can say that data and variables that are wrapped in a function are only accessible to members of that function. The scope chain is what links firstNum to number().

When number() is invoked, the non-technical conversation goes like this…

Engine: “Number, I’m giving you a new execution context. Let me find what you need to run” Engine : “Ok, I see that thirdNum is explicitly declared. I’m setting space aside for you, go to the top of number’s function block and wait till I call you… Engine : “Number, I see secondNum, does he belong to you?” Number : “Nope.” Engine : “Ok, I see you’re linked to the global window object, let me look outside of you.” Engine : “Window, I have an identifier named secondNum, does he belong to you?” Window : “He didn’t declare himself explicitly in Number with a var, let, or

const, so I’ll take him and set space aside.”Engine: “Cool. Number, I see firstNum in your function block, does he belong to you?”Number: “Nope.”Engine: “Window, I see firstNum being used inside of Number, he needs him, does he belong to you?”Window: “Yes, he was declared.”Engine: “Everyone is accounted for, Now I’m assigning values to variables.”Engine: Number, I’m executing you, ready, go!”

That’s pretty much it for understanding scope, The key takeaways are:

  1. Identifier lookup works from the inside out and stops at the first match.
  2. There are two types of scope, global and local
  3. The scope chain is created at function invocation and is based on where variables and/or blocks of code are written (lexical environment). Are variables or functions nested?
  4. In JavaScript, if an identifier is not proceeded with a var, let, or const, it is implicitly declared in the global scope.
  5. Scope does not go 1 for 1 with a function, it goes 1 to 1 with function invocation. Execute a function 3 times, get 3 different scopes. Why? Because if the execution of a function is finished, it is popped off the execution stack and with it, its access to other variables via its scope chain. Thus, a new scope is created each time a function is executed. Closures work a little differently!

Let’s finish up with a more complex example before we move on to closures.

a = 1;var b = 2;
function outer(z) { b = 3; c = 4; var d = 5; e = 6;
function inner() { var e = 0; d = 2 * d; return d; } return inner(); var e;}outer(1);
  1. Before we run anything, hoisting is started at the outside, global level. Therefore we start with a declaration for a variableb, and a function declaration for function object outer. At this point nothing is assigned, we only have these two keys set up in the global scope variable object.
  2. Next, we start at a = 1. This is an assignment, or a “write to” statement, yet there is no formal declaration for it. So what happens in the global scope, and if not in “strict mode”, is that a will be implicitly declared as belonging to the global scope variable object.
  3. We move to the next line and look up identifier b, through hoisting it was accounted for and now we can assign a value, 2, to it.

So far we have…

Global Scope

4. Since we built the function object outer, at hoisting time, we then jump to execution, outer(1);

5. Remember that upon function invocation, an execution context is first created. With that we create an Activation Object. It contains data and variables local to that context. We also form the scope chain.

6. The parameter z is implicitly declared for this function and is assigned 1.

A quick side note: at this time, the function’s execution context creates its “this” binding. It also creates an arguments array, which is an array of parameters passed, in this case, z. This is beyond the scope of this article, so allow me to glance over it.

7. Now we look for explicit variable declarations in function outer. We have d, and var e is declared after the function inner.

8. Here’s some hidden magic, at this time a hidden [[scope]] property for function outer links its scope chain of variable objects. In this case, it works like a Linked List with a parent type property connecting the function outer Activation Object to the global execution context’s Variable Object. You can see here that scope extends from the inside out to form this “linking”. This is the reference that allows us to proceed up the scope chain for lookups.

Scope for Function outer

9. We step inside of outer and start at b = 3. Is b declared? Nope. So JavaScript uses the hidden [[scope]] property attached to function outer to move up the scope chain to find a “b”. It finds it in the global scope object and, since we are in the body of function outer, we assign b the value 3.

Global Scope again

10. Next line, c = 4. Since this is a write to identifier c, was c explicitly declared in function outer? No, and therefore it is not found by lookup in outer’s Activation Object. So it moves up the scope chain and looks in the global scope Variable Object. It is not there. Because this is a write to/ assignment operation, the global scope will handle it and place it on its Variable Object.

Global Scope Variable Object

11. d = 5. Yes, it is here so we assign it 5.

Scope for function outer

12. e = 6. Remember that straggler, var e? It was still declared in the body of outer and so we already had a place for it — so we assign it 6. If it wasn’t declared like c, we would move up the scope chain for a lookup. Since it is a write and not a read operation and not in ‘strict mode’, it would have been placed in the global’s scope.

13. We get to invoking function inner. We start all over like we did with function outer: hoisting, set up an Activation Object, and create a hidden [[scope]] property. In this case, the containing context is function outer, and outer “points” to the global scope.

Scope for function inner

14. Now with e and in general, variables that are given the same name work like this. Since identifier lookup starts from the innermost scope to the outermost scope, lookup stops at the first finding of that identifier. In the body of inner, we see var e= 0, done, stop, go no further. The e in the body of function outer is “inaccessible”. The term that is commonly used is “shadowing” e in function inner “shadows” or obscures the e in function outer.

15. Next line is d = 2 * d. Before we assign a value to d on the left, we have to evaluate the expression on the right, 2 * d. Since d is not local in scope to inner, we move up the scope chain to find a variable for d and whether it has a value associated with it. We find it in the outer scope in function outer and it is there that the value is changed.

Scope for function outer

The important thing here is that inner is manipulating data in its outer scope!

16. Function inner returns a value d, 10.

17. Function outer returns the value of function inner.

18. Result is 10.

19. Once function outer has completely finished executing, garbage collection takes place. Garbage collection is the freeing up of resources that are longer needed. It starts at the global scope and works as far as it has “reachability”.

The global scope in this example has no handle to function outer or function inner, so whoosh, gone. This is important when we get to closures, because there, we need data and some variables to stick around even after a function has finished running.

Finally, let’s get some Closure!

How shall we define a closure?

Let’s start with a few definitions, all correct, some more in depth, but that get to the same point.

1. Closures are functions that have access to variables from another function's scope. This is accomplished by creating a function inside another function.
2. A Closure is a function that returns another function.
3. A Closure is an implicit, permanent link between a function and its scope chain.

Why Closures?

Without being able to leverage scope chain rules, async operations would be impossible. Because there is no guarantee that data will still be around to use later. JavaScript only has function scope as its encapsulation mechanism.

Closures are the best form of privacy for functions and variables. This is evident in the use of many module patterns. A module pattern returns an object to expose a public API. It also keeps other methods and variables private. Closures are used in event handling and callbacks.

An example of a module …

var Toaster = (function(){ var setting = 0; var temperature; var low = 100; var med = 200; var high = 300; // public var turnOn = function(){ return heatSetting(); }; var adjustSetting = function(setting){ if(setting 3 && setting  6 && setting <= 10){ temperature = high;
}return temperature; }; // private var heatSetting = function(adjustSetting){ var thermostat = adjustSetting; return thermostat; }; return{ turnOn:turnOn, adjustSetting:adjustSetting };})();
Toaster.adjustSetting(5);Toaster.adjustSetting(8);

The module Toaster has private locals and a public interface and is written as an Immediately Invoked Function Expression (IIFE). We create a function, immediately invoke it, and grab the return value.

Another small example:

function firstName(first){ function fullName(last){ console.log(first + " " + last); } return fullName;}var name = firstName("Mister");name("Smith") // Mister Smithname("Jones"); //Mister Jones

The inner function fullName( ) is accessing the variable, first, in its outer scope, firstName( ). Even after the inner function, fullName, has returned, it still has access to that variable. How is this possible? The inner function’s scope chain includes the scope of its outer scope.

When a function is called, an execution context and a scope chain are created. Also the function get’s a hidden [[Scope]] property. The Activation Object for the function is initialized and placed in the chain. Then the outer function’s activation object is placed in the chain. In this case, finally the global Variable Object.

In this example, fullName is defined. A [[Scope]] property is created. The containing function’s activation object is added to fullName’s scope chain. It is also added to the global variable object. This reference to an outer function’s activation object enables access to all of the containing scopes variables. It does not get garbage collected.

This is most important. The activation object of the outer function, firstName(), cannot be destroyed once it is finished executing, because the reference still exists in fullName’s scope chain. After firstName( )

execution completes, its scope chain for that execution context is destroyed. But the activation object will remain in memory until fullName( ) is destroyed. We can do that by setting its reference to null.

Den ivrige observatør vil bemærke, at vi returnerer en reference til fullName, ikke returværdien af ​​fullName ()!

Dette er hvad vi mener med en implicit, permanent sammenhæng mellem og funktion og dets omfangskæde.

En lukning får altid den sidste værdi fra den indeholdende funktion, fordi henvisningen til det variable objekt er lagret.

For eksempel …

var myFunctions= [];function createMyFunction(i) { return function() { console.log("My value: " + i); }; }for (var i = 0; i < 10; i++) {myFunctions[i] = createMyFunction(i);myFunctions[i]();}
My value: 0 My value: 1 My value: 2 My value: 3 My value: 4 My value: 5 My value: 6 My value: 7 My value: 8 My value: 9

Hvis vi går tilbage til vores oprindelige anvendelseseksempel og ændrer en ting:

a = 1;var b = 2;
function outer(z) { b = 3; c = 4; var d = 5; e = 6;
function inner() { var e = 0; d = 2 * d; return d; } return inner; // we remove the call operator, now we are returning a reference to function inner. var e;}myG = outer(1); // store a reference to function inner in the global scope (the return value of outer)myG(); // when we execute myG, inner's [[Scope]] property is copied to recreate the scope chain, // and that gives it access to the scopes that contain function inner, outter then global. We got inner and inner's got outter.

Her er et par flere eksempler:

function make_calculator() { var n = 0; // this calculator stores a single number n return { add: function(a) { n += a; return n; }, multiply: function(a) { n *= a; return n; } };}
first_calculator = make_calculator();second_calculator = make_calculator();
first_calculator.add(3); // returns 3second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33second_calculator.multiply(10); // returns 4000

Antag, at vi ønskede at udføre en række funktioner:

function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { result.push(function number(i) { var item = 'item' + list[i]; console.log(item + ' ' + list[i])} ); } return result;}buildList([1,2,3,4,5]);
function testList() { var fnlist = buildList([1,2,3,4,5]); for (var i = 0; i < fnlist.length; i++) { fnlist[i](i); // another IIFE with i passed as a parameter!! } } testList();

Jeg håber, at denne forklaring af omfang og lukninger hjælper. Spil rundt med de mønstre, du ser her, eksperimenter. Det var faktisk svært at skrive denne artikel - jeg fik en langt dybere forståelse, end jeg havde, da jeg startede.

Ressourcer

YDKJS

Dmitry Soshnikov, Javascript: Core

ECMA 262.3

StackOverflow

Nick Zakas