- JavaScript’s Memory Model
- Ethan Nam
- Translator: Chor
// Declare some variables and initialize them
var a = 5
let b = 'xy'
const c = true
// reassign
a = 6
b = b + 'z'
c = false // TypeError: Assignment to constant variable
Copy the code
Declaring variables, initializing, and assigning values is something we programmers do almost every day. But what do these operations actually do? How does JavaScript handle this internally? More importantly, how does it benefit us programmers to know the low-level details of JavaScript?
The outline of this paper is as follows:
- Variable declarations and assignments of JS primitive types
- JS memory model: call stack and heap
- JS reference type variable declarations and assignments
- Let vs const
Variable declarations and assignments of JS primitive types
Let’s start with a simple example: declare a variable named muNumber and initialize it to 23.
let myNumber = 23
Copy the code
When this line of code is executed, JS will be……
- Create a unique identifier for variables (
myNumber
) - Allocate a block of stack memory (allocation will be done at run time)
- Store the value 23 in this allocated space
We’re used to saying “myNumber equals 23,” but a more precise way of saying it is the address of the memory space that holds the value 23. The distinction between the two is critical and needs to be clarified.
If we create a new variable, newVar, and assign myNumber to it……
let newVar = myNumber
Copy the code
. Since myNumber is actually equal to the memory address “0012CCGWH80”, this operation makes newVar also equal to “0012CCGWH80”, which is the memory address holding the value 23. Eventually, we might get used to saying “newVar is now equal to 23”.
So, what happens if I do this?
myNumber = myNumber + 1
Copy the code
MyNumber will “equal” 24, but newVar and myNumber refer to the same memory space.
Will not. In JS, the basic data type is immutable, and when “myNumber + 1” is resolved to “24”, JS will actually reallocate a new chunk of memory to hold the value 24, and myNumber will instead refer to the address of the new memory space.
Here’s another example of a type:
let myString = 'abc'
myString = myString + 'd'
Copy the code
JS beginners might think that this operation concatenates the character D after the string, no matter where the string ABC is stored in memory. This is wrong. Remember that strings are also primitive types in JS. When ABC and D are concatenated, a new space is allocated in memory to hold the string ABcd, and myString will instead refer to the address of the new memory space (while ABC is still in the original memory space).
Let’s take a look at where basic types of memory allocation occur.
JS memory model: call stack and heap
In simple terms, we can think of the JS memory model as consisting of two different regions, one is the call stack, one is the heap.
In addition to function calls, the call stack is also used to hold basic types of data. Using the code in the previous section as an example, after declaring variables, the call stack can be roughly represented as follows:
In the figure above, I’ve abstracted the memory location to show the value of each variable, but remember that (as mentioned earlier) variables always point to a chunk of memory that holds a value. This is the key to understanding the let vs const section.
Let’s look at the heap.
The heap is where reference type variables are stored. A key difference between a heap and a stack is that a heap can hold dynamically growing, unordered data — especially arrays and objects.
JS reference type variable declarations and assignments
When it comes to variable declaration and assignment, there is a big difference between the behavior of reference type variables and primitives.
Let’s also start with a simple example. Let’s declare a variable named myArray and initialize it to an empty array:
let myArray = \[\]
Copy the code
When you declare a variable myArray and assign it a value by referring to type data such as [], the in-memory operation looks like this:
- Create a unique identifier for variables (
myArray
) - Allocate a block of heap memory (allocation will be done at run time)
- This space holds previously assigned values (empty arrays)
[]
) - Allocate a space in stack memory
- This space holds the address to the allocated heap space
We can do various array operations on myArray:
myArray.push("first")
myArray.push("second")
myArray.push("third")
myArray.push("fourth")
myArray.pop()
Copy the code
Let vs const
In general, we should use const as often as possible, and only use let when we are sure that the variable will change.
Here’s the point. Notice what the change actually means.
Many people mistakenly think that “change” here refers to a change in value, and may try to explain it with code like the following:
let sum = 0
sum = 1 + 2 + 3 + 4 + 5
let numbers = []
numbers.push(1)
numbers.push(2)
numbers.push(3)
numbers.push(4)
numbers.push(5)
Copy the code
Yes, it is correct to use let to declare the sum variable, after all, the sum variable does change; However, it is wrong to declare numbers with let. The root of the error is that they think adding elements to an array is changing its value.
The so-called “change” actually refers to the memory address change. Let declares variables that allow us to modify memory addresses, whereas const does not.
const importantID = 489
importantID = 100 // TypeError: Assignment to constant variable
Copy the code
So let’s look at why we got an error here.
After the importantID variable is declared, some memory space is allocated to hold the value 489. Remember what we said before, the variable importantID is never equal to a memory address.
When 100 is assigned to importantID, a new block of memory is allocated to hold 100, since 100 is a value of basic type. After that, JS tries to assign the address of this new space to importantID, and an error is reported. This is exactly what we wanted, because we didn’t want to change the very important ID……. at all
This makes sense. It is wrong to declare an array with a let. Const should be used instead. This can be confusing for beginners, because it’s totally counterintuitive! A beginner might wonder why we need to use const since an array is const and a const const is unchangeable. However, you must keep in mind that by “change” I mean a change in memory location. Let’s take a closer look at why using const here is perfectly fine and definitely a better choice.
const myArray = []
Copy the code
After declaring myArray, the call stack allocates a block of memory that holds a value pointing to an address of the allocated memory in the heap. This space in the heap is where the empty array is actually stored. Take a look at the picture below to understand:
If we do these operations:
myArray.push(1)
myArray.push(2)
myArray.push(3)
myArray.push(4)
myArray.push(5)
Copy the code
This will add elements to the array in the heap. However, the memory address of myArray remains the same all the way through. This explains why myArray is declared as const, but changes to it are not reported. Because myArray is always equal to the memory address “0458AFCZX91,” which points to the space that holds the other memory address “22VVCX011,” which points to the space that actually holds the arrays in the heap.
If we do this, we get an error:
myArray = 3
Copy the code
Since 3 is a basic value, doing so allocates a new chunk of memory for 3 and changes the value of myArray to be equal to the address of the new chunk. Since myArray is declared as const, this modification is bound to report an error.
Doing the following will also return an error:
myArray = ['a']
Copy the code
Because [‘ a ‘] is a new array of reference type, a new space is allocated in the stack to hold a space address in the heap, and this space in the heap is used to hold [‘ a ‘]. Then we try to assign the new memory address to myArray, which obviously also gives an error.
An object declared as const behaves just like an array. Because objects are also reference data, you can add keys, update values, and so on.
const myObj = {}
myObj['newKey'] = 'someValue' // this will not throw an error
Copy the code
What good does it do to know that?
According to GitHub and Stack Overflow’s annual Developer Survey, JavaScript is the no. 1 language. Mastering this language and becoming a “JS master” may be what we dream of. Any decent JS course or book will advocate that we use const and let more and var less, but they basically don’t explain why. Many beginners wonder why some variables declared with const do report errors when “modified” while others do not. I can understand that this counterintuitive experience makes them prefer to use lets everywhere, since nobody wants to step on holes.
However, this is not the way we recommend it. Google, a company of top programmers, has a JavaScript style guide that declares all local variables as const or let. Unless a variable needs to be reassigned, it is declared const by default. Never use the var keyword (source).
They didn’t say why, but I think there are some reasons:
- Avoid future bugs in advance
- with
const
Declared variables must be initialized at the time they are declared, which leads developers to focus on how they behave in scope, ultimately helping to promote better memory management and performance. - For better readability, anyone who takes over the code will know which variables are unmodifiable (as far as JS is concerned) and which can be reassigned.
Hopefully this article has helped you understand why and when to declare variables using const or let.
Reference:
- Google JS Style Guide
- Learning JavaScript: Call By Sharing, Parameter Passing
- How JavaScript works: memory management + how to handle 4 common memory leaks