Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for JavaScript Closure Simply Explained
Phillip Shim
Phillip Shim

Posted on • Edited on

     

JavaScript Closure Simply Explained

 

A closure is a stateful function that is returned by another function. It acts as a container to remember variables and parameters from its parent scope even if the parent function has finished executing. Consider this simple example.

functionsayHello(){constgreeting="Hello World";returnfunction(){// anonymous function/nameless functionconsole.log(greeting)}}consthello=sayHello();// hello holds the returned functionhello();// -> Hello World

Look! we have a function that returns a function! The returned function gets saved to a variable and invoked the line below.

Many ways to write the same code!

Now that you know what a closure is at a basic level, here are few ways to write the same code as above.

// originalfunctionsayHello(){constgreeting="Hello World";returnfunction(){// anonymous functionconsole.log(greeting)}}// #1functionsayHello(){constgreeting="Hello World";returnfunctionhello(){// named functionconsole.log(greeting)}}// #2functionsayHello(){constgreeting="Hello World";functionhello(){// named functionconsole.log(greeting)}returnhello;// return inner function on a different line}// #3functionsayHello(){constgreeting="Hello World";consthello=()=>{// arrow functionconsole.log(greeting)}returnhello;}

Pick a style you like the most and stick with it because every one of the above variations will still print the same result!

consthello=sayHello();hello();// -> Hello World

 

Benefits of closure and how it can be practical

 

Private Namespace

It's cool that the inner function remembers the environment that it was created in but what use does it have? A couple. First,it can keep your variables private. Here is the classic counter example.

functioncounter(){letcount=0;returnfunction(){count+=1;returncount;}}constincrement=counter();console.log(increment());// 1console.log(increment());// 2console.log(count)// Reference error: count is not defined

Trying to access the count variable gives us a reference error because it's not exposed to the global environment. Which helps us reduce bugs because our state is more strictly controlled by specific methods.

Reusable states

Because 'count' is privately scoped, we can create different instances of counter functions and their 'count' variables won't overlap!

functioncounter(){letcount=0;returnfunction(){count+=1;returncount;}}constincrementBananaCount=counter();constincrementAppleCount=counter();console.log(incrementBananaCount());// 1console.log(incrementBananaCount());// 2console.log(incrementAppleCount());// 1

Module design pattern

The module design pattern is a popular convention to architect your JavaScript apps. It utilizes IIFE(Immediately Invoked Function Expression) to return objects and exposes only the variables and methods that you want to make public.

letDog1=(function(){letname="Suzy";constgetName=()=>{returnname;}constchangeName=(newName)=>{name=newName;}return{getName:getName,changeName:changeName}}())console.log(name);// undefinedDog1.getName()// SuzyDog1.changeName("Pink")Dog1.getName()// Pink

As soon as this code runs, the function executes and returns an object which gets saved to Dog1. This pattern goes back to keeping our namespace private and only revealing what we want as public methods and variables via form of an object. The state is encapsulated!

 

The famous interview question

What's the outcome of running the following function?

for(vari=0;i<5;i++){setTimeout(function(){console.log(i)},1000)}

Why is this such a popular interview question? Because it tests your knowledge of function scope/block scope, closure, setTimeout and anonymous function! The answer prints out five 5s after 1 second.

55555

How? Well, setTimeout runs 5 times in the loop after 1 second. After the time delay, they execute functions inside, which simply logs out i. By the time 1 second has passed, the loop already finished and i became 5. Five 5s get printed out. Not what you were expecting? You probably want to see number 1 through 5 iteratively.

The solution

There are a few solutions, but let's focus on using closure!

for(vari=0;i<5;i++){setTimeout((function(index){returnfunction(){console.log(index);}}(i)),1000)}

We have our closure that is returned by an anonymous function to receive current 'i's as arguments and output them as 'index'. This in doing so captures the current variable i to each function. The result turns out to be

0 (...1000ms have passed)1 (...1000ms have passed)2 (...1000ms have passed)3 (...1000ms have passed)4 (...1000ms have passed)5 (loop exits)

 

Congratulations! 🎉🎉 Now you are more prepared for your next interview! 😉 Just remember that closure is a function that has access to the scope that encloses that function.

 

Top comments(9)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
jmergenthal profile image
Jurian Mergenthal
  • Joined
• Edited on• Edited

Just a small addition:
instead of

for(vari=0;i<5;i++){setTimeout((function(index){console.log(index)})(i),1000)}
Enter fullscreen modeExit fullscreen mode

you can also just uselet, instead ofvar here:

for(leti=0;i<5;i++){setTimeout((function(){console.log(i)}),1000)}
Enter fullscreen modeExit fullscreen mode

sincelet variables are bound to the block scope :)

CollapseExpand
 
yamalight profile image
Tim Ermilov
Hi, I'm Tim. I talk about webdev, javascript and video games.
  • Location
    Germany
  • Education
    PhD
  • Work
    Postdoc at DICE, University of Leipzig
  • Joined
• Edited on• Edited

The code in the final example is actually wrong:

for(vari=0;i<5;i++){setTimeout((function(index){console.log(index)})(i),1000)}

If you write like this, you might as well removesetTimeout completely as it's not doing anything here (try setting timeout to 10s and see if it prints out the resultafter 10s).
What you are doing is this:

functionprintIndex(index){console.log(index)}for(vari=0;i<5;i++){setTimeout(printIndex(i),1000)}

This will just print out all index immediately andsetTimeout will have no effect.
To fix this - you either need to return a function, or usingbind when setting a timeout, e.g.:

functionprintIndex(index){return()=>console.log(index)}for(vari=0;i<5;i++){setTimeout(printIndex(i),1000)}
CollapseExpand
 
shimphillip profile image
Phillip Shim
  • Location
    Austin
  • Joined
• Edited on• Edited

Oops. you are totally correct, forgot to return a function!. I appreciate you pointing this out. Will fix the code example!

CollapseExpand
 
bigschatz profile image
bigschatz
  • Joined

Hi Phillip,

Thank you for writing this great article on closures. The practical examples are easy to understand.

I wonder how, in the last example of the for loop, could you refactor the example so that each index prints out after the setTimeout value has elapsed

Like this:
0 (...1000ms have passed)
1 (...1000ms have passed)
2 (...1000ms have passed)
3 (...1000ms have passed)
4 (...1000ms have passed)
5 (loop exits)

CollapseExpand
 
shimphillip profile image
Phillip Shim
  • Location
    Austin
  • Joined

Sure just made an edit!

CollapseExpand
 
bigschatz profile image
bigschatz
  • Joined

I see that you made the edit!

But that is not actually what happens when you run the code.

The code you wrote prints 0-4 (all indexes) after 2000ms all at once.

I was curious how to make a loop that prints out each index, one at a time, according to the setTimeout value.

Maybe this isn't possible using a for loop.

I hope that makes sense!!

Thread Thread
 
jaydeepkarena profile image
Jaydeep Karena
React, Javascript, Typescript, Nodejs, Mongodb, GraphQL, MySQL, SqLite

They print all at once because all setTimeout starts at same time and for all setTimeout 1 second passes at same time. What you want can be achieved by passing timeout values to 1000, 2000, 3000....

Following code will give you that output:

for(var i=0; i<5; i++) {  setTimeout((function(index) {    return function() {      console.log(index);    }  }(i)), 1000 * i)}
CollapseExpand
 
rctryoka profile image
Robert Carlo Tubig
  • Joined
• Edited on• Edited

in the interview question about closure
why does it shows a different result when you use let in the for loops ?

CollapseExpand
 
salyadav profile image
Saloni Yadav
I am a believer of the Root Cause Analysis. Ask "How, What, Why" until you can dissect it with no further to go. Well, I am a frontend developer ready for new challenges!
  • Email
  • Location
    Atlanta
  • Education
    MS in CS, Georgia Tech
  • Work
    Ex-SWE @ Oracle | Vimeo
  • Joined

let is a block scope. so the value of i gets captured. in case of var its a global scope/scope of where the function is called. so when the function actually executes, the var i value is the last standing value, which is not the case with let.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

  • Location
    Austin
  • Joined

More fromPhillip Shim

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp