JavaScript Memory Model - Primitive Data Type vs. Reference Data Type, Immutable, Constant, Variable
Preliminary Concepts
Bits and Bytes
Bit is the smallest unit an information can be stored in computer. Computer can signify different data via increase or decrease of electric pressure. Representing electric pressure under or above certain voltage, a bit can represent two data; 0 and 1. Though 1 and 0 is not much, when combined, bits can represent more information. For instance, 8 bits combined are called 1 byte.
Since a bit can represent 2 data, 1 byte can represent 256(2^8) data. The number seems sufficient to represent meaningful data, such as numbers and strings.
Byte Identifier = Memory Address
Memory consists of numerous bytes and data in memory are accessible by unique identifiers of each byte. We call this unique identifier 'memory address.'
Variable Identifier = Variable Name
In certain context, identifier and variable are used to refer same thing. In this post, we mean by variable data that might vary. More broadly, it's a kind of container that accommodates variable data. On the other hand, when we say identifier in terms of variable, we designate a sort of label on a container, namely a variable name.
Primitive Data Type
Let's start by taking a look at how value of primitive data type is assigned to a variable. When we write the following JavaScript code,
const a = 'abc'
computer assigns value 'abc'
to variable a
through following steps:
- find a free memory space and name it
a
(51
) - get a memory address of a slot where value is
abc
Look for memory space where value isabc
Since none is found, find another free memory space and store valueabc
(2003
) - search in memory where its name is
a
and set as its value reference to memory address acquired in step 2(@2003
)
Note that computer doesn't store value(abc
) directly in a memory space with name a
. Computer stores data in one area of memory and reference to the data in another area. In this post, in light of the book "Core JavaScript," we call the former 'data area' and the latter 'variable area.'
The motivation of such separation is better memory management via data re-use. Imagine, for example, that one declares 500 variables with same value abc
.
const a = 'abc'
const a2 = 'abc'
const a3 = 'abc'
const a4 = 'abc'
...
const a500 = 'abc'
Suppose that a slot in variable area takes up 2 bytes and 8 bytes in data area. If the JavaScript engine were to secure independent 500 slots in data area for all 500 variables, it would have taken up 5000(500 * 2 + 500 * 8) bytes in total. Instead, JavaScript engine shares slot in data area for a same value and end up taking only 1008(500 * 2 + 8) bytes. The following is the mechanics of memory allocation:
- assign value
abc
to variablea
as stated above
1 - 1. find a free memory space and name ita
(51
)
1 - 2. get a memory address of a slot where value isabc
.
Look for memory space where value isabc
. Since nothing is found, find a free memory space and store valueabc
(5004)
1 - 3. search in memory where its name isa
and set as its value reference to memory address acquired in step 2(@2003
) - assign value
abc
to variablea2
2 - 1. find a free memory space and name ita2
(52
).
2 - 2. get memory address where value isabc
.
Found 502, where valueabc
is contained.
2 - 3. search in memory where its name isa2
and set as its value reference to memory address acquired in step 2(@2003
)
Immutability
Immutable vs. Constant
The term 'immutable' and 'constant' are often confused. Now that we made distinction between variable area and data area, we can make differentiate between 'immutable' and 'constant' accordingly; Being immutable means immutability of data in data area, while being constant concerns whether data in variable area can be reassigned. Let's see in details what it means by looking at immutability of primitive data type.
Modifying Data
Let's say we'd declared variable a
with let
keyword and assigned new value to it.
let a = 'abc'
a = 'abc' + 'def'
Not that reassigning new value to a variable doesn't change data in data area. Instead, only data in variable area has changed to point to new memory address where new value is stored. As such, data in data area is kept as it is, unless released from memory through garbage collection.
Copying Data
We've seen earlier that when creating multiple variables with same value, JavaScript engine optimizes by pointing at same memory address with the value. Same happens when we copy variable's value like the following code:
let a = 'abc'
let a1 = a
But what happens when we makes changes to copied variable a2
?
let a = 'abc'
let a1 = a
a1 = 'def'
Again, data in data area is left unchanged and only data in variable area changed.
variable vs. constant variable
We said earlier that being constant concerns variable area. JavaScript lets us declare constants using const
keyword. When declared with const
keyword, data in variable area, not only those in data area, cannot be altered.
const a = 'abc'
a = 'def' // TypeError: Assignment to constant variable
Reference Data Type
Now, let's continue looking at how memory is allocated when we declare a variable with reference data type. Take the following code snippet and visual representation of the mechanics.
When we execute the following code,
const obj1 = {
a: 10,
b: 'abc'
}
JavaScript engine
- find a free memory space(
71
) and name itobj1
- When trying to store assigned value in data area, it recognizes that the value is of compound type.
2 - 1. JavaScript Engine secures extra slots(88
,89
) in memory's variable area for each key ofobj1
and name them after property name(a
andb
; in the case of an array, index is used to allocate memory).
2 - 2. search in memory where its name isobj1
and set as its value reference to range of memory addresses acquired in step 2 - 1(@88~
)
2 - 3. for values of each property, search in memory's data area whose value corresponds to it and retrieve its address(312
,313
). If none is found, find a free memory space and set property's value as data.
2 - 4. go back to variable area memory in memory identified by each respective property name(a
,b
) and set as its value reference to memory address acquired in step 2 - 3(@312
,@313
).
Reference data types don't store value directly in data area(3001
) whose memory address is referenced by memory identifiable by variable's name(obj1
). Instead, they take up extra space in variable area for their properties and each property sends us to part of memory where actual values are stored. When it comes to storing data, they occupy data area in memory just as plain, primitive data types do.
const obj1 = {
a: 10,
b: 'abc'
}
const obj2 = {
a: 10,
b: 'abc'
}
obj1.a === obj2.a // true
obj1.b === obj2.b // true
obj1 === obj2 // false
Immutability
We can modify reference data types like the following.
const arr = [1, 2, 3]
arr.push(7) // arr [1, 2, 3, 7]
const obj1 = {
a: 10,
b: 'abc'
}
obj1.a = 11 // obj1 { a: 11, b: 'abc' }
However, modification can mean different things at time, which requires different visual layout for respectively. First, let's consider a scenario where data of reference data type are altered only partly.
Modifying Data
- Changing Property
In the following example, we are changing value of property a
of obj1
.
const obj1 = {
a: 10,
b: 'abc'
}
obj1.a = 11 // obj1 { a: 11, b: 'abc' }
Unlike modifying values of primitive data type(e.x. abc
→ abcdef
), changing property's value of reference data type didn't change the memory address that variable name obj1
is referencing. However, while it seems from outside that data part of obj1
is updated while, we see from the above layout that again, it's actually the variable part of obj1
(a
at memory address 314
) that has changed. For reference data type also, data stored in data area are immutable and it's only those in variable area that are bothered.
2. Reassignment
But what if we assign entirely new value to reference type data, as we did earlier to primitive type?
let obj1 = { c: 10, d: 'abc' }
obj1 = { x: 20., y: 'def' }
In this case, value of obj1
has changed, and the data it now refers to is completely independent of the old ones({ x: 20., y: 'def' }
)
Copying Data
When copying variable with value of primitive data type, changing value of copied variable didn't affect original variable's value. However, copying works differently with reference data type. Consider following code, for example.
let a = 10
let b = a
const obj1 = {
a: 10,
b: 'abc'
}
const obj2 = obj1
b = 15
obj2.a = 15
a === b // false
console.log(obj1) // { a: 15, b: 'abc' }
obj1 === obj2 // true
Changing copied object obj2
's property changed obj1
's property as well. Also, they're still evaluated as same by identity operator(===
). The reason why is described in the following visual model.
That two copied variables are still considered same after accessing one of the variable and modifying its value means that they are still looing at the same memory address and only the property variable part of the object has changed.
variable vs. constant variable
Just like value with primitive data type, if value of reference type is assigned to variabled clared with let
keyword, it can both be altered and reassigned. When assigned to variable declared using const
, data are partly modifiable while complete reassignment still throws type error.
const arr = [1, 2, 3]
arr.push(7) // arr [1, 2, 3, 7]
const obj1 = {
a: 10,
b: 'abc'
}
obj1.a = 11 // obj1 { a: 11, b: 'abc' }
obj1 = {
c: 'completely',
d: 'different'
} // Uncaught TypeError: Assignment to constant variable.
let obj2 = {
a: 10,
b: 'abc'
}
obj2 = {
x: 'this',
y: 'will,
z: 'work'
}
References
- 코어 자바스크립트(Core JavaScript), 재남, 정. 위키북스, 2019. pp. 13-20.