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.

[Figure1]

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

[Figure2]

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:

  1. find a free memory space and name it a (51)
  2. get a memory address of a slot where value is abc
    Look for memory space where value is abc
    Since none is found, find another free memory space and store value abc ( 2003)
  3. search in memory where its name is a and set as its value reference to memory address acquired in step 2(@2003)
[Figure3]

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:

  1. assign value abc to variable a as stated above
    1 - 1. find a free memory space and name it a (51)
    1 - 2. get a memory address of a slot where value is abc.
    Look for memory space where value is abc. Since nothing is found, find a free memory space and store value abc (5004)
    1 - 3. search in memory where its name is a and set as its value reference to memory address acquired in step 2(@2003)
  2. assign value abc to variable a2
    2 - 1. find a free memory space and name it a2(52).
    2 - 2. get memory address where value is abc.
    Found 502, where value abc is contained.
    2 - 3. search in memory where its name is a2 and set as its value reference to memory address acquired in step 2(@2003)
[Figure4]

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'
[Figure5]

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
[Figure6]

But what happens when we makes changes to copied variable a2?

let a = 'abc'
let a1 = a
a1 = 'def'
[Figure7]

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
[Figure8]

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

  1. find a free memory space(71) and name it obj1
  2. 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 of obj1 and name them after property name(a and b; in the case of an array, index is used to allocate memory).
    2 - 2. search in memory where its name is obj1 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).
[Figure9]

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.

💡
Note that distinction between primitive and reference data type can be misleading. Both data types don't store value directly in memory that is named after variable name. Instead, reference to another memory space is stored. In a sense, they both reference another place in memory. The difference between them is the number of reference. As to primitive data type, reference occurs only once, while there are at least two references as to reference data types, or even more, depending on how much an object is nested. 
💡
Remember when we declared two different variables with same value that is of primitive data type? In case of reference data type, two variables with 'seemingly' same value do not evaluate to same, suggesting that two objects point to different memory address in variable area.
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

  1. 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' }
[Figure10]

Unlike modifying values of primitive data type(e.x. abcabcdef), 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' }
[Figure11]

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.

[Figure12]

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.

💡
So far, we've seen that data of reference data type are considered mutable when partly modifying their properties. However, under certain circumstances, you want to change properties of object but leave original object intact. In order to do so, various techniques are used, such as creating new, deep-copied object and only manipulating properties of new object. Such concept of 'immutable object' is important for Functional Programming paradigm and for state management in SPA frameworks or libraries like React, Vue.js, and Angular. 

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.

Subscribe to go-kahlo-lee

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe