Variables: A brief overview
In JavaScript
(JS), prior to ECMAScript 6 (ES6) there was only one way to declare a variable: you had to use the var
keyword followed by the variable name. ES6 was officially standardized in 2015 and introduced many features that modern JS programmers take for granted. This article will focus on:
Variable declarations
How the keyword you use for the variable declaration will determine the scope (availability) of your data/datum.
JavaScript
is a lexically scoped language, which means that variables are accessible within the scope in which they are declared and any inner scopes (nested blocks or functions). They are not available outside of their scope.
Variable interpolation (template literals)
How to interpolate (insert) those variables (and complex expressions) into strings.
When we declare a variable, we do so to conveniently reference a piece of data in memory. To review, there are three ways to declare a variable in JavaScript
var
has a function-level scope or global scope.let
andconst
have a block-level scope (i.e., scoped to loops, conditionals, and functions).Both
let
andvar
allow you to declare a variable that can be reassigned to a new value.let name = "marco"; name = "jimbo"; console.log(name); // "jimbo" var year= 2024; year = 2023; console.log(year); // 2023
const
is used for variables whose value remains constant and cannot be reassigned once initialized.
const name = "marco"; name = “derek”; // Uncaught TypeError: Assignment to constant variable
Scope: what is it?
Scope, like many things in life, is one of those concepts in programming that most people don’t care about until it trips them up. But it is important because scope determines where data in your program can be accessed. Before we dive into the JavaScript
, let’s use the physical world as an example.
Let’s say you’re in your house and had to make a phone call. Would you use your neighbor’s landline (does anyone still use those anymore?) or the closest phone to you, likely your cell phone? I hope you’d use the phone closest to you, if only for the sake of not bothering your neighbor!
In JavaScript
(and other programming languages like PHP
) scope is just like that. To go back to the phone example when searching for variable phone
JavaScript
will prioritize the variable in the scope nearest to it. Let’s say that you’re in a function
and you log a variable to the console
to inspect it. There is a variable with the same name in the global scope. Like this:
var phone = {
type: "landline",
location: "neighbor"
};
function makePhoneCall() {
var phone = {
type: "cellPhone",
location: "home",
};
console.log("makePhoneCall: ", phone.type);
}
makePhoneCall(); // "makePhoneCall: " "cellPhone"
As you can see, the phone
variable inside makePhoneCall()
is prioritized over the phone
variable in the global scope.
Another important concept scope comes up in is closures. A closure in JavaScript creates a two-way mirror effect, where a function retains access to variables from its outer scope (looking out), while the outer scope cannot directly access or modify variables inside the function (can't look in). This behavior allows functions to remember data and create a state as they are called.
function createCounter() {
let count = 0; // This variable is in the enclosing (outer) scope
function increment() {
count++; // The inner function can access 'count' from the enclosing scope
}
function getCount() {
return count; // The inner function can also return the value of 'count'
}
return { increment, getCount }; // Return an object with references to the inner functions}
const counter = createCounter();counter.increment();// Increment the count variable
console.log(counter.getCount()); // Output: 1
counter.increment(); // Increment again
console.log(counter.getCount()); // Output: 2
In this example, createCounter
is a function that creates a counter
using a closure. The count
variable is defined in the outer scope of createCounter
, and both increment
and getCount
functions are defined within this scope as well. The increment
function can access and modify the count variable, while the outer scope (outside of createCounter
) cannot directly access the count
variable.
By returning an object with references to the inner functions (increment
and getCount
), we expose a controlled interface to interact with the count
variable from the outside. The outside code can only interact with the counter
through the functions provided by the closure, effectively creating the “two-way mirror” effect.
In the phone example above, we looked at the var
keyword and its scope (function or global depending on where it is declared). Let’s do the same thing with let and const.
function exampleLetScope() {
let x = 10; // This variable has block scope
if (true) {
let x = 20; // This creates a separate 'x' variable within the block scope
console.log(x); // Output: 20
}
console.log(x); // Output: 10, still accessible outside the block scope
}
exampleLetScope();
function exampleConstScope() {
const pi = 3.14159; // This variable has block scope and is constant
if (true) {
const pi = 3.14; // A separate constant "pi" is created within this block
console.log(pi); // Output: 3.14
}
console.log(pi); // 3.14159 (the original "pi" remains unchanged outside the block)
}
exampleConstScope();
Template literals
Template literals have a couple of advantages over ‘single’ or “double” quotes. To name a few:
Support of multiline strings without needing to use explicit escape characters. Ask yourself which looks better
//using escape characters, namely the newline "\n" character
const multilineMessage = 'This is a multiline string.\n' +
'It spans across multiple lines.\n' +
'This requires using escaped characters (\\n).';
console.log(multilineMessage);
// This is a multiline string
// It spans across multiple lines
// This requires using escaped characters (\n)
using template literals
const multilineMessage = `This is a multiline string.
It spans across multiple lines
without the need for escape characters.`;
console.log(multilineMessage);
// This is a multiline string.
// It spans across multiple lines
// without the need for escape characters.
Let’s compare methods of using variables in strings. Template literals or quotes, you decide!
const name = 'John';
const message = "Hello, " + name + "!";
console.log(message); // Output: Hello, John!
// using template literals to get the name intro the message
const name = 'John';
const multilineMessage = `Hello, ${name}!
This is a multiline string.
It spans across multiple lines
without the need for escape characters.`;
console.log(multilineMessage);
/* the output:
"
Hello, John!
This is a multiline string.
It spans across multiple lines
without the need for escape characters.
"
*/
Maybe I’m just lazy and don’t want to have to press the shift button to access the “+”, but `backticks` just seem faster. Template literals make short work of variable interpolation!
const name = 'John';
const message = `Hello, ${name}! Welcome to our website.`;
console.log(message); // Hello, John! // Welcome to our website.
Ask yourself: which is more readable and extensible? Do you still find yourself using var
instead of the more modern let
or const
? What are the use cases for var
, let
, and const
? Can you explain scope as you would to a child, and do you remember which variables are block VS function scoped? What’s the difference?
Let me know in the comments your answers and any questions you may have. Thanks for reading.