We often talk about scope differences between var, let and const. But even more often, I see learners still struggling with fully grasping the idea. I think it’s probably because concepts are rarely visualized.
Let’s literally — take a look.
Not all scopes are made alike.
Note: I don’t recommend memorizing the scope variable definition and value visibility rules for each type of scope. Instead, try to figure out the actual reasons for why it actually works this way. (Variable privacy, for example.)
Simple block scope accessibility rules:
Global scope variables can get into inner block scope. Block scope variables defined with var are hoisted back into the global scope but the value is not transitioned (only variable name definition) and becomes undefined.
But global scope to function scope & back is basically a one-way street:
Global variables can get into function scope but can’t get out into global scope when they are defined inside the function.
Hints Of The Closure Pattern
Functions enable the closure pattern because their variables are hidden from global scope but can still be accessed from other function scopes within them:
The idea is to protect variables from global scope but still be able to call the function from it. We’ll take a look at this in greater detail in just a moment.
Protecting variables from outside scope helps reduce bugs in the long run. You may not see it right away. But if you stick with this idea, you will avoid causing bugs in the future that don’t really have to happen.
No Difference In Global Scope
With so many “differences between var, let and const” tutorial headlines it is easy to believe that the 3 are completely different. But this isn’t always true.
When variables are defined in global scope there is no differences between var, let and const in terms of scope visibility.
Keywords let and const limit the variable to the scope in which they were defined:
Variables defined using let and const are not hoisted. Only var is.
In Function Scope
However, when it comes to functions, all variable types, including var, remain
limited to their scope:
You cannot access variables outside of the function scope in which they were
defined regardless of which keyword was used.
Part of the function closure pattern is a function trapped inside another function. The inner function returns the counter variable. But because the add() function itself is the closure container, it never leaks into global scope.
The trick? The counter variable is hidden from global scope. But we can still call add() from global scope. In other words, the counter variable remains private… but… we can access it from global scope as the closure’s return value.
Okay, that was an abstract visual representation. But what does a closure actually look like in code?
This basically means that we define an anonymous function and evaluate it as a statement. It’s the same as when we do something like (1).toString() because 1.toString() would generate an error.
Why Are We Doing This?
The plus() function is defined by an anonymous function that executes itself. Inside the scope of plus, another anonymous function is created. It increments a private variable counter and returns the result back to global scope.
Take away: Global Scope cannot access nor modify the counter variable at any time. The closure pattern allows its inner function to modify the variable without the variable leaking into Global Scope…
The whole point is that Global Scope does not need to know or understand how the code inside add() works. It only cares about receiving the result of the add() operation so it can pass it to other functions, etc.
Closures are no longer as popular as they used to be. They were invented in the pre-ES6 era. Their functionality will be replaced in the future when private variables are added to the definitions made with class keyword.
So why did we even bother explaining them?
This is also sometimes called encapsulation — one of the key principles of Object Oriented Programming where inner workings of a class member method are hidden from the environment from which they were called.
If you think about it, this is exactly why let was invented. It provides automatic privacy for variables defined in block-level scope. Variable privacy is a fundamental feature of many programming languages in general.
In Local Scope
The let and const keywords conceal variable visibility to scope in which they
were defined and its inner scopes.
Scope visibility differences surface when you start defining variables inside local block-level scope or function-level scope. As we discussed earlier in global scope there is no difference.
The class scope is simply a placeholder. Trying to define variables directly in
class scope will produce an error:
Here are the proper places for defining local variables and properties. Note, let (or var or const) only create a local variable to that scope. Therefore, it cannot be accessed from class methods.
In classes, a variable is defined inside its constructor function or its methods:
The this Keyword In Class constructor Function
The this keyword is used to define object properties. Once defined in constructor, they become available for access in all class methods via the this keyword (or rather, reference to an object).
Think of this as the instance of the object defined by that class.
Quirk: The this keyword is available inside constructor before the object is
instantiated — but only as a means to assign new properties to the object.
Using this in methods implies that the object has already been instantiated.
Local Variable Definitions
Local variables can be defined using var, let or const keywords, but they will
remain limited to the scope of the constructor or the method in which they were defined, without actually becoming properties of the object. Remember that constructor and methods are still technically just function scopes.
Here’s how it all might look like in code:
Dissecting a class: constructor and a method meow(). There is only one constructor that creates the instance of the object. But you can have as many methods as you want. Think of methods as actions your class can do.
For a cat it could be: meow(), eat(), drink(), sleep(). Which actions should your class have? It depends on the purpose and type of the object your class defines.
It’s an abstract model. You are the designer of your class. Go crazy.
Note that because felix object was actually created in global scope, you can access it within the class methods (but not in class constructor, because in constructors the instance of the object is still being instantiated.)
But why use felix inside meow() class when we can simply use this keyword to refer to the same thing? It was just an example.
The const keyword is distinct from let and var.
It doesn’t allow you to re-assign a previously defined variable to a new value:
It’s still possible to change values of a more complex data structure such as Array or objects, even if variable was defined using const. Let’s take a look!
const and Arrays
Changing a value in the const array is still allowed, you just can’t re-assign the object to a different array:
You just can’t assign a new value to the original variable name. Once array always array. Or once an object always an object:
const and Object Literals
When it comes to object literals, the const only makes the definition constant.
But it doesn’t mean you can’t change values of the properties assigned to a variable that was defined with const:
And that’s it for today! Thanks for reading!