Variables and Values
Variables and Values
Overview
When we start learning to program, the first things we have to understand are variables
and values. But I see that many people get confused with these basic concepts. There are
lots of FALSE statements that variables keep what we assigned in them like a box or
primitive values are stored in stacks,…. The thing is, lots of developers use variables
without really knowing how they work. Today, let's talk about what variables and values
really are, and correct misconceptions by peeking under the hood at the JavaScript
language implementation to see how values are stored in memory.
Mental model for variables:
Imagine your code as a busy planet filled with instructions. In the world of JavaScript,
values are like floating things in space.
Think of primitives as faraway stars—they're there, but you can't touch or change them.
Now, objects are like floating ships nearby. They have connections to other ships or
primitive planets.
Remember that: Primitives are like natural and unchangeable things, kind of like forces
of nature. Objects are more like things we make and can control.
Enter variables—they're like the people on your code planet. Their job is to connect with
and control the ships or keep an eye on the primitive planets.
I won't get too deep into this idea, but seeing it in this way might make understanding
JavaScript variables and values a bit easier.
You might be thinking: why do we have to add extra pointer indirections between values
and variables/object properties? This (mostly) has to do with the nature of the language
being dynamically typed. In a dynamically typed programming language, the type checks
are performed at runtime.
However JavaScript variables can hold any type of values at runtime. Therefore, the
types cannot be associated with a variable but with the underlying value
pointed/referenced by a variable. To achieve that, we need a uniform memory
representation for JavaScript variables - pointers. And the real values are allocated
somewhere else on the heap (dynamic memory), containing metadata such as the type
and the size of the value, pointed by those pointers.
JavaScript cannot do pass-by-reference
But there are contrary opinions misunderstand that JavaScript objects are assed-by-
references, but that's not quite right. JavaScript doesn't actually do pass-by-reference.
When you store an object in a variable, it's not a direct alias of the object itself but a copy
of the address where the object lives in the heap.
Now, if you give that variable a new value, it doesn't replace the original object. It just
points to a different place in memory. But because it still has the address, it can access
and change the contents (like properties) of the original object
Primitive values are also allocated on the heap
Another claiming you can read on resources online that JavaScript primitive values are
allocated on the stack while objects are allocated on the heap. This idea is false, at least
this is not how the language is implemented in the majority of JavaScript engines.
Contrary to common belief, primitive values are also allocated on the heap, just like
objects. To prove this statement, let’s inspect one of the most popular JavaScript engines
– V8.
“JavaScript values in V8 are represented as objects and allocated on the V8 heap, no
matter if they are objects, arrays, numbers or strings.” - v8 blog
First, use command node --v8-options | grep -B0 -A1 stack-size to get the
default size of stack in V8. My result is 984kB.
We use this script to inspect the memory in heap before and after creation of 10MB string
The size of the heap memory used before we created the string was 4.01 MB
After I created a string of a size of 10 MB, the heap memory used increased to 14.01 MB
The difference is exact the size of string we created. See the stack size we printed out
before, it was only 984kB - there is no way the stack can store such a string.
When debugging v8 on small string, we can see that string are located in Old Space of Heap memory.
String internal
A quick question: If we assign 2 variables to the same string. Does it result in the
duplication of the string in memory, or end up with 2 strings allocated on the heap?
The answer to this question depends on the way you create strings:
In order to efficiently process strings, the V8 engine maintains a string constant pool.
Strings are stored in the string constant pool.
name1 refers to a string in string constant pool directly while name2 refers to an object in
heap.
So what is the difference?
If we add 2 variables to the code:
We can find a funny truth: name1 and name3 point to the same location,
while name2 and name4 point to two different objects.
Let’s inspect details in chrome webtool:
This code will create an array arr of length 10000 of "bytefish" and another array of
length 10000 of new String(“bytefish”)
When inspect in chrome devtools
Although there are 10,000 elements in the array, they all have the same address
@271085. This verifies the existence of the string constant pool we mentioned earlier.
About new String(“bytefish”), new object will be created each time we declare new one,
increasing memory assumption.
Oddballs
There are a special subset of primitive values called Oddball in V8.
They are pre-allocated on the heap by V8 before the first line of your script runs - it
doesn’t matter if your JavaScript program actually uses them down the road or not.
They are always reused - there is only one value of each Oddball type.
When we are creating JavaScript variables that "have" Oddball values, we should think as
if they were "summoned" in our JavaScript program - we cannot create or destroy them.
Number
All numbers in JavaScript are represented as floating-point values. JavaScript represents
numbers using the 64-bit floating-point format (like double in C)
64 bits:
For pointers, V8 will always set the last bit to 1. If that bit is 1, it means we are dealing
with a pointer. That also means that before using that pointer, we need to clear that last
bit (set it to 0) because it was set to 1 just to mark that variable as a pointer.
For small integers (SMI), the last bit will be set to 0. That means that small integers are
31bit long on both 32 bit and 64 bit systems. ([-230, 230-1])
So technically, a smi can exist on the stack since they don’t need additional storage
allocated on the heap, depending how the variables are declared:
Another complication about numbers is, unlike other types of primitive values, they
might not get reused:
- For smis, they are encoded as recognizably invalid pointers, which don't point to
anything, so the whole concept of "reusing" doesn't really apply to them.
- For HeapNumbers (numbers that are not considered smis) can and do get reused,
but when they are pointed by object's properties, it becomes a mutable
HeapNumber, which allows updating the value without allocating a new
HeapNumber every time. If you simply had a function f() { return 12.5; }, it would
actually return the same reused HeapNumber every time it's called.
So now let me revise the statements we have concluded today for completeness:
• JavaScript objects are not passed by reference but involve copying the object's
memory address
• All JavaScript values are allocated on the heap accessed by pointers no matter if
they are objects, arrays, strings or numbers (except for small integers (smi) in V8
due to pointer tagging).
• Primitive values are (mostly) reused
Reading materials:
https://v8.dev/blog/react-cliff
https://v8.dev/blog/pointer-compression
https://docs.google.com/document/d/11T2CRex9hXxoJwbYqVQ32yIPMh0uouUZLdyrtmMoL44/edit#he
ading=h.jvrqdo9eoczl