1. JavaScript 배열은 배열이 아니다?
전통적인 의미의 배열(Dense Array)은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 구조를 말합니다.
하지만 자바스크립트의 배열은 내부적으로 해시 테이블로 구현된 객체입니다.
🔍 전통적인 배열 vs JS 배열
- 전통적인 배열: 인덱스로 즉시 주소 계산이 가능하여 접근 속도가 매우 빠르지만(O(1)), 메모리 낭비가 발생할 수 있습니다.
- JS 배열: 인덱스가 사실상 '객체의 키'입니다. 연속되지 않은 메모리 공간을 가질 수 있으며, 인덱스로 접근할 때 일반적인 배열보다는 느릴 수 있지만, 유연한 구조를 가집니다.
V8 엔진의 최적화: 실제로 JS 엔진은 성능을 위해 배열의 요소가 조밀할 경우(밀집 배열), 내부적으로는 C++의 배열처럼 연속된 메모리를 할당하여 속도를 최적화합니다.
2. 희소 배열(Sparse Array)과 메모리 효율
희소 배열이란 배열의 요소가 연속적이지 않고 중간에 '구멍(Hole)'이 뚫린 배열을 말합니다.
const sparse = [];
sparse[0] = "A";
sparse[10000] = "B";
console.log(sparse.length); // 10001
메모리는 어떻게 할당될까?
위 코드에서 자바스크립트는 10001개의 칸을 예약하지 않습니다. 내부적으로는 다음과 같은 객체 구조로 관리됩니다.
- 값
"A"를 저장하는 데이터 메모리 - 값
"B"를 저장하는 데이터 메모리 length속성값10001을 저장하는 공간- 기타 관리용 정보
즉, 중간에 비어있는 1번부터 9999번까지의 인덱스에 대해서는 어떠한 메모리도 할당하지 않습니다. 이것이 자바스크립트가 희소 배열을 메모리 효율적으로 관리하는 비결입니다.
3. length 속성의 비밀
JS 배열에서 length는 실제 요소의 개수를 세는 카운터가 아닙니다.
length는 **(가장 큰 인덱스 + 1)**을 나타내는 숫자입니다.심지어
length는 쓰기(Writable)가 가능합니다.length를 강제로 줄이면 배열의 요소가 실제로 삭제되지만, 크게 늘린다고 해서 메모리가 미리 확보되지는 않습니다.const arr = [1, 2, 3]; arr.length = 100; // length는 100이 되지만, 추가 메모리 할당은 없음 console.log(arr[50]); // undefined (실제로 존재하지 않는 Hole)
4. 배열의 정적 메서드 (Static Methods)
블로그 제목에서 언급한 정적 메서드는 특정 배열 인스턴스가 아닌 Array 생성자 자체에서 호출하는 메서드입니다. 희소 배열을 다루거나 배열을 생성할 때 매우 유용합니다.
① Array.from()
유사 배열 객체(Array-like object)나 이터러블을 배열로 변환합니다. 희소 배열의 '구멍'을 undefined로 채우면서 밀집 배열로 만들 때 자주 사용됩니다.
// 길이가 5인 희소 배열을 밀집 배열로 변환
const dense = Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]
② Array.isArray()
전달된 인자가 배열인지 확인합니다. typeof 연산자가 배열을 object로 반환하기 때문에 반드시 이 정적 메서드를 사용해야 합니다.
③ Array.of()
전달된 인자를 요소로 갖는 배열을 생성합니다. new Array(3)이 길이 3인 빈 배열을 만드는 것과 달리, Array.of(3)은 요소 3을 하나 가진 배열을 만듭니다.
5. 성능 최적화 팁: 구멍(Hole)을 피해야 하는 이유
희소 배열은 메모리 측면에서는 유리할 수 있지만, 런타임 성능에는 치명적일 수 있습니다.
엔진 최적화 해제: V8 엔진은 배열의 요소가 'Holey'한 상태가 되면, 더 이상 연속된 메모리 구조(Fast Elements)를 유지하지 못하고 일반 객체 모드(Dictionary Elements)로 전환합니다. 이는 접근 속도를 현저히 떨어뜨립니다.
순회 메서드의 동작:
forEach,map,filter등은 배열의 구멍(Hole)을 건너뛰고 실행됩니다. 이는 예상치 못한 버그를 유발할 수 있습니다.const sparse = [1, , 3]; // 중간이 빈 희소 배열 sparse.forEach(v => console.log(v)); // 1, 3만 출력됨 (Hole은 무시)
🎯 요약
- 자바스크립트 배열은 내부적으로 객체이며, 인덱스를 키로 사용한다.
- 희소 배열은 실제 값이 있는 부분만 메모리를 사용하므로 거대한 공간을 미리 차지하지 않는다.
length는 실제 요소의 개수가 아닌 최대 인덱스 + 1을 의미한다.- 성능을 위해서는 가급적 밀집 배열(Dense Array) 형태를 유지하는 것이 최적화에 유리하다.