JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at any moment, passed as an argument to another function, and then called from a totally different place of code later. Show
We already know that a function can access variables outside of it (“outer” variables). But what happens if outer variables change since a function is created? Will the function get newer values or the old ones? And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place? Let’s expand our knowledge to understand these scenarios and more complex ones. We’ll talk about In JavaScript, there are 3 ways to declare a variable:
Code blocksIf a variable is declared inside a code block For example:
We can use this to isolate a piece of code that does its own task, with variables that only belong to it:
There’d be an error without blocks Please note, without separate blocks there would be an error, if we use
For
Here, after That’s great, as it allows us to create block-local variables, specific to an The similar thing holds true for
Visually, Nested functionsA function is called “nested” when it is created inside another function. It is easily possible to do this with JavaScript. We can use it to organize our code, like this:
Here the nested function What’s much more interesting, a nested function can be returned: either as a property of a new object or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables. Below,
Despite being simple, slightly modified variants of that code have practical uses, for instance, as a random number generator to generate random values for automated tests. How does this work? If we create multiple counters, will they be independent? What’s going on with the variables here? Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let’s go a bit in-depth. Lexical EnvironmentHere be dragons! The in-depth technical explanation lies ahead. As far as I’d like to avoid low-level language details, any understanding without them would be lacking and incomplete, so get ready. For clarity, the explanation is split into multiple steps. Step 1. VariablesIn JavaScript, every running function, code block The Lexical Environment object consists of two parts:
A “variable” is just a property of the special internal object, In this simple code without functions, there is only one Lexical Environment: This is the so-called global Lexical Environment, associated with the whole script. On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, that’s why the arrow points to As the code starts executing and goes on, the Lexical Environment changes. Here’s a little bit longer code: Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution:
Everything looks simple for now, right?
Lexical Environment is a specification object “Lexical Environment” is a specification object: it only exists “theoretically” in the language specification to describe how things work. We can’t get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. Step 2. Function DeclarationsA function is also a value, like a variable. The difference is that a Function Declaration is instantly fully initialized. When a Lexical Environment is
created, a Function Declaration immediately becomes a ready-to-use function (unlike That’s why we can use a function, declared as Function Declaration, even before the declaration itself. For example, here’s the initial state of the global Lexical Environment when we add a function: Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable,
such as Step 3. Inner and outer Lexical EnvironmentWhen a function runs, at the beginning of the call, a new Lexical Environment is created automatically to store local variables and parameters of the call. For instance, for During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global):
The
inner Lexical Environment has a reference to the When the code wants to access a variable – the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one. If a variable is not found anywhere, that’s an error in strict mode (without In this example the search proceeds as follows:
Step 4. Returning a functionLet’s return to the
At the beginning of each So we have two nested Lexical Environments, just like in the example above: What’s different is that, during the execution of All functions
remember the Lexical Environment in which they were made. Technically, there’s no magic here: all functions have the hidden property named So, Later, when
Now when the code inside A variable is updated in the Lexical Environment where it lives. Here’s the state after the execution: If we call Closure There is a general programming term “closure”, that developers generally should know. A closure is a function that remembers its outer variables and can access them. In some languages, that’s not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in The "new Function" syntax). That is: they automatically remember where they were created using a hidden When
on an interview, a frontend developer gets a question about “what’s a closure?”, a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the Garbage collectionUsually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That’s because there are no references to it. As any JavaScript object, it’s only kept in memory while it’s reachable. However, if there’s a nested function that is still reachable after the end of a function, then it has In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive. For example:
Please note that if
A Lexical Environment object dies when it becomes unreachable (just like any other object). In other words, it exists only while there’s at least one nested function referencing it. In the code below, after the nested function is removed, its enclosing Lexical Environment (and hence the
Real-life optimizationsAs we’ve seen, in theory while a function is alive, all outer variables are also retained. But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it’s obvious from the code that an outer variable is not used – it is removed. An important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging. Try running the example below in Chrome with the Developer Tools open. When it pauses, in the console type
As you could see – there is no such variable! In theory, it should be accessible, but the engine optimized it out. That may lead to funny (if not such time-consuming) debugging issues. One of them – we can see a same-named outer variable instead of the expected one:
This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it. That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page. What is a variable inside a function called?Variables that are declared inside a function or block are local variables. They can be used only by statements that are inside that function or block of code. Local variables are not known to functions outside their own.
Is a variable used within a function?Variables defined inside a function cannot be accessed from anywhere outside the function, because the variable is defined only in the scope of the function. However, a function can access all variables and functions defined inside the scope in which it is defined.
What is declared inside a function and can be used only inside that function?Variables defined inside functions are called local variables. Their value can only be used within the function where they are declared.
Which type of variable can be created and used inside of a function?A local variable is defined inside a function or a block statement and is not accessible outside the function or block. This means that these variables are visible and can only be used by the function in which the variable is defined!
|