Variable Scope in Modern JavaScript
Translator: OFED
Modern JavaScript variable scope
When talking to other JavaScript developers, I’m often surprised how many people don’t know how variable scoping works in JavaScript. By scope we mean the visibility of variables in the code; Or, in other words, which parts of the code can access and modify variables. I found that people used var to declare variables in their code and didn’t know how JavaScript would handle them.
JavaScript has undergone some dramatic changes over the past few years; These changes include new variable declaration keywords and new scope handling. The let and const commands have been added to ES6(ES2015) for three years now, and browser support is good. For other new features, Babel can be used to convert ES6 into widely supported JavaScript. Now it’s time to review how variables are declared and increase your understanding of scope.
In this blog post, I’ll show you how global, local, and block-level scopes work through a number of JavaScript examples. We’ll also show you how to declare variables using let and const for those still unfamiliar with this section.
Global scope
Let’s start with the global scope. Globally-defined variables can be accessed and modified anywhere in the code (almost always, but we’ll cover the exceptions later).
Variables declared outside the top-level scope of any function are global variables.
var a = 'Fred Flinstone'; // Global variables
function alpha() {
console.log(a);
}
alpha(); // Print 'Fred Flinstone'
Copy the code
In this case, a is a global variable; Therefore, it can be easily retrieved in any function. Therefore, we can print the value of a from method alpha. When we call the alpha method, the console prints Fred Flinstone.
When a global variable is declared by a Web browser, it is treated as a property of the global Window object. Consider this example:
var b = 'Wilma Flintstone';
window.b = 'Betty Rubble';
console.log(b); // Prints 'Betty Rubble'
Copy the code
B can be accessed/modified as a property of the window object (window.b). Of course, there is no need to change the value of B through the Window object, just to prove the point. We are more likely to write the above as follows:
var b = 'Wilma Flintstone';
b = 'Betty Rubble';
console.log(b); // Prints 'Betty Rubble'
Copy the code
Be careful with global variables. They make code less readable and harder to test. I’ve seen many developers have unexpected problems finding when variable values are reset. It is much better to pass variables as arguments to functions than to rely on global variables. Global variables should be used sparingly.
If you do need to use global variables, it is best to define namespaces that make them properties of global objects. For example, create a global object called Globals or app.
var app = {}; // Global objects
app.foo = 'Homer';
app.bar = 'Marge';
function beta() {
console.log(app.bar);
}
beta(); / / output 'Marge'
Copy the code
If you are using NodeJS, the top-level scope is different from the global scope. If var foobar is used in the NodeJS module, it is a local variable to that module. To define global variables in NodeJS, we need to use the global namespace object, global.
global.foobar = 'Hello World! '; // is a global variable in NodeJS
Copy the code
Note that a variable is globally scoped if it is not declared using one of the keywords var, let, or const.
function gamma() {
c = 'Top Cat';
}
gamma();
console.log(c); // print 'Top Cat'
console.log(window.c); // print 'Top Cat'
Copy the code
We recommend always using a variable keyword to define variables. This way, the scope of each variable in the code is controllable. As in the example above, hopefully you are aware of the potential dangers of not using keywords.
Local scope
Now let’s go back to the local scope
var a = 'Daffy Duck'; // a is global
function delta(b) {
// b is the local variable passed to delta
console.log(b);
}
function epsilon() {
// c is defined as a local scope variable
var c = 'Bugs Bunny';
console.log(c);
}
delta(a); // Output 'Daffy Duck'
epsilon(); // Print 'Bugs Bunny'
console.log(b); // Throws an error: b is not defined in global scope
Copy the code
Variables defined inside a function are scoped within the function. In the example above, b and c are local to their respective functions. But what would be the output if the following were written?
var d = 'Tom';
function zeta() {
if (d === undefined) {
var d = 'Jerry';
}
console.log(d);
}
zeta();
Copy the code
The answer is ‘Jerry’, which is probably one of the most frequently asked interview questions. The zeta function internally defines a new local variable d. When a variable is defined with var, JavaScript initializes it at the top of the current scope, regardless of where it is in the code.
var d = 'Tom';
function zeta() {
var d;
if (d === undefined) {
d = 'Jerry';
}
console.log(d);
}
zeta();
Copy the code
This is called promotion, and it’s one of the features of JavaScript, and it’s important to note that variables are not initialized at the top of the scope, which can cause some bugs. Thankfully, let and Const came to our rescue. So let’s look at creating block-level scopes using lets.
Block-level scope
With the advent of ES6 a few years ago, there were two new keywords for declaring variables: let and const. Both of these keywords allow us to expand our scope to code blocks, that is, content between two curly braces {}.
let
Many see let as an alternative to the existing VAR. However, this is not entirely true, as they declare variables in different scopes. Let declares block-scoped variables, whereas the VAR statement allows us to create locally scoped variables. Of course, we can use let to declare block-level scope within functions, just as we used var before.
function eta() {
let a = 'Scooby Doo';
}
eta();
Copy the code
Here, the scope of A is in eta. We can also extend to conditional blocks and loops. The block-level scope includes any subblocks contained in the top block of the variable definition.
for (let b = 0; b < 5; b++) {
if (b % 2) {
console.log(b); }}console.log(b); // 'ReferenceError: b is not defined'
Copy the code
In this case, B operates in a block-level scope within the scope of the for loop, which includes conditional blocks. Therefore, it will print odd numbers 1 and 3, and then throw an error because we can’t access B outside of its scope.
We saw earlier that JavaScript’s strange variable promotion affected the result of zeta. What happens if we rewrite the function to use let?
var d = 'Tom';
function zeta() {
if (d === undefined) {
let d = 'Jerry';
}
console.log(d);
}
zeta();
Copy the code
This time Zeta prints “Tom” because d is limited to the condition block, but does that mean there is no promotion here? No, JavaScript still elevates the variable to the top of the scope when we use let or const. But unlike var, which elevates the variable to undefined, let and const are not initialized. They exist in temporary dead zones.
Let’s take a look at what happens when a block-scoped variable is used before the initialization declaration.
function theta() {
console.log(e); / / output 'undefined'
console.log(f); // 'ReferenceError: d is not defined'
var e = 'Wile E. Coyote';
let f = 'Road Runner';
}
theta();
Copy the code
Thus, calling Theta prints undefined for the locally scoped variable E and throws an error for the block-scoped variable f. We can’t use f until we start it, in which case we set its value to “Road Runner.”
Before we continue, there is another important difference between let and VAR. When we use var at the top level of our code, it becomes a global variable and is added to the Window object in the browser. With let, although the variable becomes global because it is scoped as a block of the entire code base, it does not become an attribute of the Window object.
var g = 'Pinky';
let h = 'The Brain';
console.log(window.g); / / output 'Pinky'
console.log(window.h); / / output is undefined
Copy the code
const
I mentioned const in passing. This keyword was introduced with let as part of ES6. In terms of scope, it works in the same way as LET.
if (true) {
const a = 'Count Duckula';
console.log(a); // Print 'Count Duckula'
}
console.log(a); // output 'ReferenceError: A is not defined'
Copy the code
In this case, a is scoped by the if statement, so it can be accessed inside the conditional statement, but is undefined outside the conditional statement.
Unlike let, variables defined by const cannot be changed by reassignment.
const b = 'Danger Mouse';
b = 'Greenback'; TypeError: Assignment to constant variable
Copy the code
However, when using arrays or objects, things are a little different. We still cannot reassign, so the following operation will fail
const c = ['Sylvester'.'Tweety'];
c = ['Tom'.'Jerry']; TypeError: Assignment to constant variable
Copy the code
However, we can modify a constant array or Object unless we use object.freeze () on the variable to make it immutable.
const d = ['Dick Dastardly'.'Muttley'];
d.pop();
d.push('Penelope Pitstop');
Object.freeze(d);
console.log(d); // Output ["Dick Dastardly", "Penelope Pitstop"]
d.push('Professor Pat Pending'); // Throw an error
Copy the code
Global + local scope
What happens when we redefine an already existing global variable in a local scope.
var a = 'Johnny Bravo'; // Global scope
function iota() {
var a = 'Momma'; // local scope
console.log(a); / / output 'Momma'
console.log(window.a); // Output 'Johnny Bravo'
}
iota();
console.log(a); // Output 'Johnny Bravo'
Copy the code
When we redefine global variables in local scope, JavaScript initializes a new local variable. In this example, we have a global variable a, and a new local variable a is created inside the function iota. The new local variable does not modify the global variable. If we want to access the value of the global variable inside the function, we need to use the global window object.
For me, the following code is easier to read, using global namespaces instead of global variables and rewriting our function with block-level scope :-
var globals = {};
globals.a = 'Johnny Bravo'; // Global scope
function iota() {
let a = 'Momma'; // local scope
console.log(a); / / output 'Momma'
console.log(globals.a); // Output 'Johnny Bravo'
}
iota();
console.log(globals.a); // Output 'Johnny Bravo'
Copy the code
Local + block-level scope
I hope the following code is what you expect.
function kappa() {
var a = 'Him'; // local scope
if (true) {
let a = 'Mojo Jojo'; // block-level scope
console.log(a); // output 'Mojo Jojo'
}
console.log(a); / / output 'question'
}
kappa();
Copy the code
The above code is not particularly readable, but block-level scoped variables can only be accessed at the defined block level. Modifying a block-level variable outside the block-level scope has no effect, and redefining variable A with let also has no effect, as shown in the following example:
function kappa() {
let a = 'Him';
if (true) {
let a = 'Mojo Jojo';
console.log(a); // output 'Mojo Jojo'
}
console.log(a); / / output 'question'
}
kappa();
Copy the code
Var, let or const?
I hope this summary of scopes gives you a better understanding of how JavaScript handles variables. Throughout the examples I’ve used var, let, and const to define variables. With the advent of ES6, we could have used lets and const instead of var.
So is var redundant? There’s no right or wrong answer, but personally, I still use VAR to define top-level global variables. However, I would use global variables conservatively and use global namespaces instead. In addition, I use const for variables that do not change, and let for the rest.
Will you eventually define variables, or will you hopefully have a better understanding of the scope scope in your code?