The Variable
Dave Cridland
Posted on March 13, 2021
A Rose By Any Other Name
let a = 1 + 1;
There's some code. It's JavaScript, but it might as well be any of a dozen (or more) other languages. Your challenge? Point to the variable.
It seems easy, except that just because I've asked you, you're thinking this might be a trick question. And it sort of is.
Let's start with the things that are not the variable for certain.
let
is a form of declaration. It's definitely not a variable, but it does cause a new variable to be created.
=
is an operator, in this case it might be the assignment operator - but might also be an initialization operator, or even a match operator, in other languages. It's causing the variable, newly declared by let
, to be created with a particular value.
1 + 1
is an expression, providing that value.
a
is what we generally call a variable. But really, it's a name. In some languages (notably C) a variable name always points to a unique value - you cannot have one variable with two names for it - and this is still technically true in C++, which really tries very hard to muddy the waters. In others, including Javascript and Python, many names can point to the same value. In most languages (possibly all) you can have values with no names at all - and if you think this is esoteric, just link of an array: one name covering lots of values.
So in some senses the variable doesn't exist in the source code at all. It is a value, held somewhere in the computer's memory, and the name merely references it - together, they make up the variable.
"Memory" here is a nebulous term. It might be that this is an actual memory location, but it could also be a CPU register. Either way, the value might change over time, and the location might move, but the identity of the value never does.
By thy name I bind thee ...
let a = {phrase: 'Hello!'};
let b = a;
b.phrase = 'Goodbye!';
console.log(a.phrase);
// Prints "Goodbye!"
What we've actually done in the first code is create a variable, initialize it with a value, and finally bind it to a name.
Javascript allows us to later bind the variable to a new name. In this little snippet, we've bound the variable to b
as well. Changing the variable's value does just that - the change is visible through both bound names.
We could also do other things, like rebinding the name to a different variable. Somewhat confusingly, Javascript does this using the same assignment operator:
let a = {phrase: 'Hello!'};
let b = {phrase: 'Goodbye!'};
let c = a;
a.phrase = 'What?';
a = {phrase: 'This one.'}; // <--
console.log(c.phrase);
// Prints "What?"
In the line marked with an arrow, we're not changing the variable (like we do in the line above), we're rebinding a
. This doesn't occur with, say, a number:
let a = 0;
let b = a;
a += 1;
console.log(a, b);
// Prints 1 0
This is so confusing that Javascript provides an alternate declaration keyword, const
, which prevents rebinding. In Java, this would be final
. It also makes numbers and other "primitive types" constant, like the const
keyword in C or C++.
It's as if the designers of Javascript, faced with a confusing capability, decided to make it more confusing.
... to my service unto death
Values have a lifetime, whereas names have a scope. These two are often (but not always) interlinked.
While the value exists, it takes up a chunk of the memory for the program (whereas names need not). The program can, if it has a reference to the value, read and change it.
While the name is "in scope", the program source can use that name - once it's "out of scope" it will cause a syntax error.
Javascript is, once more, odd here - so let's ignore it and pick the (surprisingly) simpler C.
{
int i = 0;
/* Some stuff here */
}
In C, a variable name exists from the point of its declaration until the end of the block (the brace-enclosed statements). In earlier versions of C, variables had to be defined at the top of the block, but that was easy to work around since a block can be used anywhere a single statement can be (it's how if
statements work, for example), so if you needed to, you could nest a block. Modern C allows you to declare the variable anywhere.
When the block is exited, the name falls out of scope and cannot be used anymore, and the value is instantly destroyed, its memory freed for use by something else.
C++ makes this a bit more explicit, since if the value is an object, special methods are called when the value is created (the "constructor") and when it is destroyed (the "destructor"). This means you can trivially see when an object is destroyed, and actually do something.
These values and variables - called "automatic variables" in C - are created on the program stack. You can create values with a different lifetime by creating them on the heap, but if you do this, you take responsibility for their lifetime entirely - the program will never destroy them unless you specifically ask it to. Equally, you don't create these values with a name - you'll instead get the memory location back (a kind of number, at least usually), and have to store that in turn as a more traditional variable somewhere.
Many languages prefer not to make the destruction explicit in the same way - these are known as "garbage collection" languages. Java, Python, and Javascript are all like this - objects are created by the programmer explicitly, as normal, but the language itself decides when you're no longer using them. This usually happens automatically for the programmer (which is nice) but can occasionally be confused by circular references and other problems.
const a = {friend: null};
const b = {friend: a};
a.friend = b;
b = a;
a = b.friend;
// Which cup is the ball under?
In the code above, a
references a value which references another value which references itself. Deciding when these values can be discarded is tricky.
But for the most part, this usually "just works".
In the vast majority of languages, scope works in the same way - "local" variable names created within a function are visible from the point of declaration through to the end of the function. C's nested blocks mean that some names have a reduced sub-scope of that function. Calling another function creates a new, empty scope - the variable names from the caller's scope are not visible to the callee.
Global variables - names created outside of a function - are "in scope" to everything, and since anything might change them unexpectedly, it's best to avoid these. Many languages have a module scope as well which behaves similarly.
Member variables - more properly called "object fields" - are only in scope inside the methods for that object.
Javascript is complex here, since the scope depends on how they're declared.
a = 'Implicit declaration';
var b = 'Explicit declaration';
let c = 'Let';
const d = 'Const';
let
and const
both operate the same way for scope, which is largely the same way as C as described above.
A minor difference here is that Javascript "hoists" the name creation (but not the value creation) to the beginning of the block. This is primarily of importance for the interview question, "What is Javascript variable hoisting?", and is otherwise pointless and confusing.
var
, though, creates a new variable name - which is dutifully hoisted to the beginning of the scope - but which is visible through the entire function. This is pretty weird.
function call_me() {
// aa actually created here.
console.log('Caller start:', aa);
var aa = 0;
if (aa === 0) {
var aa = 1; // <--
}
console.log('Caller end:', aa);
}
call_me();
You might think that the line marked with an arrow declares a new variable - but it doesn't, it just assigns the existing one a new value.
This behaviour is vital for, again, interview questions. Just use let
or const
.
You can also define a variable implicitly, by just assigning a value to the name. What this actually does, though, is define a new global variable (or module/file scope variable, strictly) - even if you're in a function. This is probably not what you expected to happen. Try this:
function call_me_too() {
console.log(typeof bb);
bb = 'Weird, huh?'
console.log(bb);
}
console.log(typeof bb);
call_me_too();
console.log(bb);
A summary
The moral of the story is:
- Use
const
- if you can - orlet
- if you can't. - Thank ESLint for finding this kind of stuff for you.
- Anything else is for answering interview questions.
Posted on March 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024