Closures in JavaScript[Beginner]

Closures in JavaScript[Beginner]

Introduction:

A closure is just like any function that somehow remains available after the surrounding outer scope has returned. Lets revisit the fundamental lexical scoping rule: “Functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked.”

The first part of it was explained in my article on Execution Context .Lets check the other part.

Technically, all JavaScript functions are closures.But most of the functions are invoked using the same scope chain that was in effect when the function was defined.

Closures becomes interesting when they are invoked under different scope chain than the one that was in effect when they were defined. So to bring this scenario, we need to make sure that the function in inner scope [where it was defined] remains available in the outer scope somehow even after the outer scope has returned so that when we invoke the inner function, the scope is not the same as it was when the function was defined.

e.g In scope.js file nested function appVersion is defined in appDetails’s function scope but to test our scenario, we need to access appVersion from outside of appDetails’s function scope. We can achieve this in couple of ways as listed below:

Technique 1: Saving the nested function (appVersion) object in a global variable.

Technique 2: The nested function (appVersion) object is returned from the function(appDetails) within which it was defined.

Technique 3: Passing the nested function to setTimeout function[it delays the execution].

Technique 1: Lets take the same code which I wrote to explain Execution Context article and add a new function variable newAppDetails and use global variable versions[] array to store the nested function object in it with versions.push(….) as shown below:


Step 1: Let me go to Chrome console and see the values of global scope variables. As you can see, the versions array is empty now, newAppDetails refers to a function {f} and appInc has random string “ewxam3”.

In-memory Diagram (before newAppDetails execution):

Step 2: Lets execute the newAppDetails function for the 1st time.?

As soon as we execute newAppDetails, a new execution context is created. We get a random string?“y91p” bound to local variable name and the code versions.push(….) adds the inner function object into versions array. If you expand the array in console,it shows the function as closure. Please check below:

In-memory Diagram (After newAppDetails 1st execution):

Step 3: You can check the value of versions array, which shows that it stores the inner function object which was defined inside the newAppDetails function scope.

Now if you execute the nested function using versions[0]() from console (outside the function scope where it was defined),a new execution context i.e blue block will be created but where? Inside black block? Or inside red block? Also what will be looged in console depends on where the new context is created.

You might think that the new execution context should be created in global scope i.e inside black block since we are executing the function in global scope and also you might guess log output as [“ewxam3”,<undefined>,”u5”] but it is not.

It would be created inside the newAppDetails function scope i.e within red block because of the fundamental rule of lexical scoping: “Functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked”. Also, please note that the execution context for a function will always be created as a child of the context that it was defined within.

Now the console.log([appInc,name,version]) statement (inside the function stored in versions[0] ) uses the name = “y91p”.

This is the effect of closures. The nested function is available outside the newAppDetails function in array but it was bound/closed under the inner scope chain in which the variable name was bound to the value ”y91p” (where the nested function was defined). The binding remains in effect when nested function is executed, no matter wherever it is executed from.

This is the surprising and powerful nature of closures: they capture the local variable(and parameter) bindings of outer function within which they are defined.

Note: So far we are focusing on local variable bindings and we have not talked about function’s parameter bindings ye. We will see it in next closure technique.

If you got the logic of how the Execution Context is created in JavaScript run time, it would be easier to get the closure concept.

In-memory Diagram (After versions[0]() 1st execution):

If I execute versions[0]() again,I get new context(blue block) with version=”fk” as shown below:?

In-memory Diagram (After versions[0]() 2nd execution):

Step 4: Now, we will execute the newAppDetails function 2nd time, a new execution context i.e red block will be created.If we check the versions array in console, we find two function objects are stored in it.

We get a new random string in “4p21” bound to local variable name and the code “versions.push(….)” adds the inner function object into versions array as 2nd function object.

In-memory Diagram (After newAppDetails 2nd execution):

Step 5: We can check the value of versions array, which shows that it has two function(which were defined inside the newAppDetails function scope).

Now if we execute the nested function stored from the 1st execution of newAppDetails again using versions[0]() from console[which is global scope], a new execution context is created inside 1st red block because of the fundamental rule of lexical scoping and closures as explained in step 3 above.

In-memory Diagram (After versions[0]() 3rd execution):

Step 6: Similarly ,if we execute the 2nd nested function stored in versions[1]() from console[which is global scope], a new execution context is created inside 2nd red block as explained in step 3 above.

In-memory Diagram (After versions[1]() 1st execution):

Step 7: You can check some more execution on your own to check if you are getting expected result as per the concepts learned so for. Below are few more executions and corresponding in-memory diagram:

Technique 2:

Before I jump into this technique, lets check out the infamous loop problem: It happens that you used a loop to assign value of looping variable “i” in someway (e.g to an html element) and found that it just returned the last value “i” had.

I am using closure.js and closureTest.html file for this example which are shown below:

Here in the closure.js,we are just creating five links and appending to the html body of the page which has just <h1> tag. Also we are registering event on each link to log corresponding link number on console.

In-memory diagram (Before opening html in browser):

Step 1: Let me open the closureTest.html in my Chrome browser. Below is what you should see:

Now if you click on any link and check the console, you will find number “5” logged always. But the expected behavior is to see corresponding link number. So where is the issue? Lets try to outline the in-memory diagram for this.

In-memory diagram (After opening html in browser):

When the html page is loaded, windows fires onload event and addLinks function is invoked which in turns create new execution context i.e red block here. Now, you might be wandering why did I keep the value I as number “5” inside function block.

Let’s remember the concepts of lexical scoping in JavaScript. It says looping statement such as for/if/while etc does not create new execution context or inner scope.

Actually, JavaScript does not have block scope. Variables introduced with a block are scoped to the containing function or script.

Back to the point, since variables(e.g i,link,breakline) introduced inside for loop does not create the separate scope rather are scoped under containing function which is addLinks here. So we have added all the variables inside for loop inside function context. Now once the loop breaks the final value of?looping variable “i” is 5 which is what is assigned to “i”when the function returns after execution.

Step 2: Let me click on the link and see what changes will be in in-memory diagram.

In-memory diagram (On clicking on any link on the page):


When we click on any link, the inner function which is stored as object on onclick event registered on each link gets executed. Here the scope variable i was having the final value 5 when the nested click event function was defined inside outer function. Hence the i value will be as 5.

Every click creates a new context i.e blue block but it uses the same outer scope i value 5.

Step 3: You can use closures (Technique 2) to fix the loop problem. Lets change the onclick event to include the code to return the nested function object which will ultimately get assigned to onclick event.

At first glance, the line 11 of code looks like it is assigning a function as a property to “link”object variable. In fact, the code is defining and invoking(check the opening and closing parenthesis after closing curly braces) a function, so it is the return value of the function that is being assigned to onclick event property on link object. For each iteration inside loop, the current value of variable i is passed as the parameter to the outer function during its invocation and its get bound to the nested function returned from it. Do you remember the parameter binding that I mentioned earlier, here it comes.

Closures feature: they capture the local variable( and parameter ) bindings of outer function within which they are defined.

Hence, the self invoking function creates a new scope for every loop cycle, so the five blue block with new context is generated.

In-memory diagram (After page load with new code):

Step 4: Now if you click on the link, it invokes the event which executes the nested function (as shown in blue block). Now the nested function has access to scope variable x hence the console.log(x) logs the expected output on console. Here the new context will be created inside blue context but that will have access to variable x so no issues.

Lets say I clicked on link 4, hence the in-memory diagram will be as shown below:

In-memory diagram (On clicking on any link on the page):

Technique 3: 3rd technique to have access to nested function in the outer scope even after the outer function has returned is passing function to setTimeout.

setTimeout() method on Window object schedules a function to run after a specified number of milliseconds elapses.

Again before we jump into the the closures , lets check the issue without using closure first.

Lets say we would like to log five consecutive numbers (starting from zero) into console at once. Below lines of code will make it for us.

for (var j = 0; j < 5 ; j++) {

???????????console.log("j = "+j);

};
        

Now lets say we want the consecutive numbers but it should appear on console with a delay 1 seconds after each number.

The closure.js and closureTest.html file for this example which are shown below:

The now() method returns the number of milliseconds since?January 1, 1970?00:00:00 UTC.https://www.epochconverter.com/ check this to convert epoch time to human readable date.

Step 1: Let me open the closureTest.html in my Chrome browser. Below is what you should see in the console:

Yes, you are seeing it correctly , the logs shows j = 5 for each delayed function execution. But why? So here again same issue with loops and function scope.

The for loop does not create a new scope and by the time loop ends and before the first delayed function(out of five) is invoked, the final value of j is 5 which is what will be bound to j variable in the global scope(since the for loop is in global scope). Once the function invocation starts , one by one five new execution context is created but they share the same outer scope value for j variable. This is why we see the same log every time on console.

In-memory diagram (After page load):

Step 2: We will use closures?to fix this issue and yes it’s the same (Technique 2) will be used. Hope you would have already guessed the code changes to be done, here is what I came up with:

Console Output:

The explanation goes same here, we have bound the looping variable value inside the nested function.

In-memory diagram (After page load with new code):

Now you should be having clear understanding on closures. Here is one code snippet for you practice:

var srt1="Hello Readers";
var printString = function(text){
	return	function(){
		console.log("Text: " +text);
	};
};

var printHello = printString(str1);
printHello();

var srt1="Hello Readers";
printHello();

*Predict both printHello() output and see if it justifies.
        

The basic concepts on lexical scope & in-memory scope in JavaScript are very important to have clear understanding on?"closures"?. You can check my articles on In-memory Scope/Execution Context.

You can refer Java Script - The Definite Guide and google for more info on closures.

Happy Learning....?????????Gaurav Singh

要查看或添加评论,请登录