const arr = []
와 같이 배열을 상수로 선언해도 push
가 동작하는 이유는 무엇일까?TL;DR
- 배열은 참조 타입이기 때문에 값이 콜 스택 메모리가 아닌 힙 메모리에 저장되고 힙 메모리에 있는 값은 변경이 가능하다.
자바스크립트의 메모리는 다음과 같이 두 가지로 나뉜다:
let a = 126;
a = 127;
자바스크립트에서 원시 타입(primitive type)은 변경이 불가능하다. 따라서 원시 타입의 값이 변경될 때는 항상 메모리가 새로 할당된다. 위 예제를 보면 변수 a
는 값 126
을 저장하는 메모리 영역 A를 바라본다. 그러나 변수 a
의 값을 127
로 변경하면, 숫자 데이터는 원시 타입이므로 메모리 영역 A의 값을 변경할 수 없어서 새로운 메모리 영역 B를 새로 할당해서 127
을 저장한다. 즉, 변수 a
가 바라보는 메모리 주소가 변경된다.
이제 다음 예제 코드를 예측할 수 있다.
let a = 126;
let b = a;
a = 127;
console.log(b)
// ?
126
이 나온다. 왜냐하면 변수 b
가 바라보고 있는 곳은 값이 변경되기 전 변수 a
가 바라보고 있던 메모리 영역이기 때문이다.
다음과 같이 배열을 상수로 선언했는데 push
로 값이 추가되는 이유는 무엇일까?
const arr = [];
arr.push(1);
arr.push(2);
console.log(arr) // [1, 2]
배열은 object
타입이라 참조 타입(reference type)으로 분류된다. 배열을 선언하면 힙 메모리에 배열이 선언된다. 콜 스택에 선언된 배열 변수는 힙에서 생성된 배열의 메모리 주소를 참조(reference)한다.
이미지 출처: medium - JavaScript’s Memory Model by Ethan Nam
힙 메모리는 동적으로 크기가 변할 수 있다. 배열에 값을 추가하면 그대로 힙 메모리에 할당된다.
이미지 출처: medium - JavaScript’s Memory Model by Ethan Nam
결론적으로 배열을 상수로 선언해도 push
가 동작하는 이유는 콜 스택에 할당된 메모리를 변경하는 것이 아니라 힙 메모리를 변경하는 것이기 때문이다.
다음 코드를 예측할 수 있다.
const a = [1, 2, 3];
const b = a;
a[0] = 100;
console.log(b[0])
// ?
100
을 출력한다. 배열은 참조 타입이므로 변수 a
가 바라보는 메모리 주소는 변경되지 않는다. 따라서 변수 b
는 변수 a
와 같은 메모리 주소를 참조한다.