If you’re a JavaScript developer, you’ve likely heard of closures, but perhaps you’re not entirely sure what they are or how they work. In this post, we’ll delve deep into the fascinating world of closures and unravel the inner workings of JavaScript functions. In this blog post, we will take a deep dive into how JavaScript functions really work. We’ll discuss concepts like , , and . higher-order functions (HOF) execution context lexical environment By the end of this journey, you’ll have a solid understanding of closures and their pivotal role in JavaScript development. Get ready to unlock the power of closures and take your JavaScript skills to the next level. Functions in JavaScript Under the Hood Under the hood, a function in JavaScript is actually an object. When a function is defined, JavaScript creates an object that holds the code of the function and additional properties like and . name length This object is stored in memory and can be referenced and manipulated like any other object in JavaScript. Furthermore, functions in JavaScript can be assigned to variables, passed as arguments to other functions, and returned as values from functions. This concept, known as functions being , allows for powerful functional programming techniques. first-class citizens Functions can be dynamically created, modified, and executed at runtime, providing flexibility and expressive power. I have added this function named to the window global object in the browser, and we can see in the console that it’s just an object with special properties and methods like : , , …etc. myFunction name length call() This is just a quick overview of Function in JavaScript to get an idea and helps us go to the next step. Higher Order Functions (HOF) After talking a bit about Functions under the hood, we will discover , which is crucial to understanding . From this point forward, we will refer to them as for the rest of this blog. Higher order function Closures HOF A simple definition of is that it is a function that can either take another function as an argument or return a function as its result. HOF Now, let’s begin with an example that does not involve the use of HOF : /* In my code I want a Function that take an array of numbers and multiply by that number */ const multiplyByNum = (arr, num) => arr.map(item => item * num); /* In another place in my code I need a function takes and array of numbers and devide them by a given number */ const devideByNum = (arr, num) => arr.map(item => item / num); We can see that in this approach we are writing duplicate core. The only difference between the two functions is the arithmetic operations. Imagine we have other operations for that array. How much will we duplicate that code? 10 This is breaking one of the main principles of Software Engineering (DRY) Don’t repeat yourself. Here is where the superpower comes into play : (HOF) // We define our (HOF) function transformArrayByNumber(array, number, operation) { return array.map((item) => return operation(item, number)); } /* That's All this is a generic (HOF) function that can do any operation we want not just multiplication and division */ const myArray = [0,1,2,3]; const multiply = (item , number) => item * number; const multiplyByNum = transformArrayByNumber(myArray, 5 , multiply) console.log(transformArray) // [0,5,10,15] // this is how we can call our (HOF) function As you can see, it gives us more flexibility and makes the code cleaner, and now, we can control our function operations only by changing the third argument . operation Execution Context When the JavaScript Engine scans the JS file for the first time, it creates environments called , and we have two types of and The is the first one that gets created when a JavaScript script first starts to run, and the is created whenever a function is called. Execution contexts Execution context Global Function. Global Function And to keep track of all contexts, JavaScript runtime uses a in this way when first it creates the and pushes it on the stack. Call Stack Global Execution context Whenever a function is invoked, similarly, the JS engine creates a function stack context for the function and pushes it to the top of the call stack, and so on. function firstFunction(string) { return string; } function secondFunction(string) { firstFunction(string) } function lastFunction(string) { secondFunction(string) } consnt result = lastFunction("Hello Nadjem") console.log(result) //"Hello Nadjem" Let’s make a diagram to demonstrate how the above script is executed: We can see, by following the diagram steps, how the Execution contexts get added and executed on and from the Call Stack. Lexical Environment Every has its own ; this Environment is a hidden associated object that contains two parts: Execution context Lexical Environment It’s a location where variables and functions declarations are stored (and some other information like the value of ). It’s located in the memory heap of the JS engine. 1 — Environment Record: this 2 To the outer ; the one associated with the outer code. — : Reference Lexical Environment I will make a Diagram right now for the previous code example with a bigger picture by including and : Execution context Lexical Environment As we can see, every has its own , which references the outer . In this case, it’s the and If a function is nested inside another function, it will have a reference to the of the outer function, which in turn, references the . This is known as . Execution context Lexical Environment Lexical Environment Global scope, Lexical Environment Global scope lexical scoping in JavaScript Closures A closure is a combination of a function and the in which it was declared. It allows a function to retain access to variables from its outer scope even after the outer function has finished executing. Lexical Environment (scope) In simpler terms, when a function is called and popped off from the it checks if there is something referencing one or some of its local variables. It saves those variables in a special box called . Call Stack, Closure function a() { let name = "Nadjem"; /* this get accessed by the b() function event after a() get called first */ function b() { let hey = "Hello"; console.log(hey + " " + name) // "Hello Nadjem" } b(); // call b() second } a(); // call a() first When I debug this code in the Chrome browser debugger, we can see more details; you can notice that the value of the variable is preserved as a because it is referenced for the of the function, and if it’s not, it will be garbage collected or deleted after we call the function . name closure Lexical Environment b() a() Conclusion In conclusion, comprehending the concepts of , and is key to understanding closures in JavaScript. higher-order functions execution context, lexical environment Closures provide a powerful mechanism for preserving data and behavior by allowing functions to retain access to variables from their outer scopes. By grasping these fundamental concepts, you can leverage closures effectively and unlock advanced programming techniques in JavaScript. Also published here