If you've already read some of the sibling posts, you can skip straight to here.
- The basics: declaring variables
- What is it?
- Okay...but what does it do?
- What is it good for?
- When should I use something else?
- So when should I use it?
Let's begin at the beginning: variable declarations declare variables. This may seem obvious to many, but in practice we often confuse variables with values, and it is important, particularly for this conversation, that we are clear on the differences.
A variable is a binding between a name and a value. It's just a box, not the contents of the box, and the contents of the box may vary either in part or in whole (hence the term 'variable').
The kind of box you use, that is, the declarator you use to create a binding, defines the way it can be handled by your program. And so when it comes to the question of, "How should I declare my variables?" you can think of the answer in terms of finding a box for your data that is best suited to the way you need to manipulate it.
functionkeyword is not, in fact, a variable declarator, but it can create a bound identifier. I won't focus on it here; it's a bit special because it can only bind identifiers to values of a particular type (function objects), and so has a rather undisputed usage.
Why so many options? Well, the simple answer is that in the beginning, there was only
var; but languages evolve, churn happens, and features come (but rarely go).
In this post, we'll dive into the behavior of
var: the original variable declarator.
varstatement declares variables that are scoped to the running execution context's VariableEnvironment. Var variables are created when their containing Lexical Environment is instantiated and are initialized to
undefinedwhen created. Within the scope of any VariableEnvironment a common BindingIdentifier may appear in more than one VariableDeclaration but those declarations collectively define only one variable. A variable defined by a VariableDeclaration with an Initializer is assigned the value of its Initializer's AssignmentExpression when the VariableDeclaration is executed, not when the variable is created.
Translation? 🤨 Let's learn by doing.
var does what it says on the tin: it names a variable and lets me use it.
During compilation, that variable is
- scoped to the nearest enclosing function (or the global scope if we're not in one)
created and initialized to
undefinedduring the instantiation of that scope, and
- available for reference anywhere within the scope at run-time
💡 You might come across the term "variable hoisting" in your JS travels: it refers to this 'lifting' behavior of the JS compiler with respect to creating variables during instantiation of the scope itself. All declarators hoist their variables, but the accessibility of those variables varies with the tool you use.
At run-time, references to my variable are evaluated and manipulated.
undefined😓 The term "not defined," in this context, should really be "not declared."
If I combined my
var declaration with a value assignment, that value doesn't go into the box until the assignment is evaluated, and evaluation happens at run-time.
Furthermore, additional declarations of the same name in the same scope using
var have no effect: it's always the same variable.
const, my box is accessible anywhere within the nearest enclosing function, not merely the closest lexical environment, and so
var really shines at function-level state management.
The ability of
var to transcend the lexical environment of blocks and let me add to the state of the nearest enclosing function is particularly powerful, if perhaps an uncommon usage.
And since functions inherit the environment of their parents thanks to closure, nested functions can access the
vars of their parents, but parents cannot assert their authority as parents and come into their children's bedrooms to mess with their
Sometimes, I only need a box to hold some data for a short while, not an entire function. Since
var scopes my data to the nearest enclosing function, it communicates "widely used" to the reader and so it's not the best tool for this job. In this situation,
let is better.
Sometimes, I want a box that only holds one thing throughout my program, and/or I want your readers to know I don't intend to make changes to the data I put in it. Since
var makes boxes that are always open to having their contents replaced, it communicates the wrong thing and so it's not the best tool for this job. In this situation,
const is better.
var inappropriately can hurt the readability and maintainability of my code because I'm communicating the wrong thing and not encapsulating my data as well as I could be.
To learn how to communicate better in my code, I dove into the other tools available and wrote about what I found:
var for holding values whose names will be referenced throughout most, or all, of the current function.
If, during the course of development, it makes sense to reduce the scope and/or accessibility of my data, I can move my declaration to a new scope and swap out
var for a tool like
function that lets me do so.
let, I am free to replace the contents of my box with something different or new at any time I might need, which makes it a great choice for tracking changes over time in situations where an immutable approach to managing function-level state is not practical to implement.
Every tool has its use. Some can make your code clearer to humans or clearer to machines, and some can strike a bit of balance between both.
"Good enough to work" should not be "good enough for you." Hold yourself to a higher standard: learn a little about a lot and a lot about a little, so that when the time comes to do something, you've got a fair idea of how to do it well.