자바스크립트에서 특정 길이의 배열을 만들고 초기화할 때, 여러분은 어떤 방식을 선호하시나요?
new Array(5).fill(0)과 Array.from({ length: 5 }, () => 0)은 결과물은 같아 보이지만,
그 속을 들여다보면 엔진이 배열을 대하는 태도부터가 다릅니다.
오늘은 이 두 방식의 동작 원리와 실무에서 어떤 것을 선택해야 할지 기준을 정리해 보겠습니다.
1. new Array(): "공간만 예약하는" 생성자
new Array(n)은 인자가 하나일 때 해당 크기만큼의 **희소 배열(Sparse Array)**을 만듭니다.
여기서 가장 중요한 키워드는 '**Hole(구멍)'**입니다.
🕳️ 'Holey' 배열의 함정
new Array(5)는 메모리에 5칸의 자리를 만들지만, 그 안에 어떤 값(심지어 undefined조차)도 넣지 않습니다.
이를 자바스크립트 엔진(V8)에서는 Holey Elements라고 부릅니다.
- 동작 방식: 인덱스만 있고 실제 데이터는 없는 상태입니다.
- 문제점:
map(),filter(),forEach()같은 고차 함수들은 인덱스에 실제 값이 존재하지 않으면 해당 요소를 아예 무시하고 건너뜁니다. - 해결책: 반드시
.fill()메서드를 통해 명시적으로 값을 채워줘야만 순회가 가능해집니다.
// ❌ map이 무시됨
const fail = new Array(3).map(() => "value"); // [ <3 empty items> ]
// ✅ fill() 이후에는 가능
const success = new Array(3).fill(null).map(() => "value"); // ['value', 'value', 'value']
2. Array.from(): "진짜 배열로 변환하는" 정적 메서드
Array.from()은 단순히 공간을 만드는 것을 넘어, **유사 배열(Array-like)**이나 **이터러블(Iterable)**을 실제 배열로 '복사/변환'하는 데 특화되어 있습니다.
✨ 왜 Array.from이 더 선호될까?
Hole이 없는 Packed 배열:
Array.from({ length: 5 })는 생성과 동시에 모든 칸을undefined로 초기화합니다. 따라서 즉시 순회가 가능합니다.Mapping 함수 내장: 두 번째 인자로 콜백 함수를 전달할 수 있어, 별도의
.map()호출 없이 생성과 동시에 값을 계산해서 채울 수 있습니다.가독성과 의도: "이 객체로부터(from) 배열을 만들겠다"는 의도가 코드에 명확히 드러납니다.
// 유사 배열 객체로부터 0~4까지 채워진 배열 생성 const arr = Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
3. 심화 비교: 성능(Performance) 관점
가독성은 Array.from이 앞서지만, 대규모 배열을 다룰 때는 이야기가 달라집니다.
| 방식 | 성능 특징 | 추천 상황 |
|---|---|---|
new Array(n).fill(v) | 가장 빠름. 엔진이 연속된 메모리를 빠르게 할당하고 값을 채움. | 10만 개 이상의 큰 배열을 단순히 같은 값으로 채울 때 |
Array.from() | 약간 느림. 변환 과정과 콜백 함수 호출 오버헤드가 있음. | 소규모 배열 생성, 인덱스 기반 값 계산, 유사 배열 변환 시 |
[...Array(n)] | 가장 느릴 수 있음. 전개 연산자의 오버헤드가 큼. | 가독성이 중요한 아주 짧은 배열 생성 시 |
🚀 실무 가이드라인
- 단순히 같은 값으로 채울 때:
new Array(n).fill(0)이 성능상 가장 유리합니다. - 인덱스별로 다른 값이 필요할 때:
Array.from({ length: n }, (_, i) => i)가 가독성과 유지보수 면에서 뛰어납니다. - 유사 배열(NodeList 등)을 다룰 때: 고민 없이
Array.from()을 사용하세요.
4. 벤치마크 테스트: 실제로 얼마나 차이 날까?
가독성 면에서는 Array.from()이 압승이지만, 대량의 데이터를 다룰 때는 성능 차이를 무시할 수 없습니다. 크롬 V8 엔진 환경에서 1,000,000(백만) 개의 요소를 가진 배열을 초기화할 때의 속도를 비교해 보았습니다.
🧪 테스트 코드
다음 코드를 브라우저 콘솔이나 Node.js 환경에서 직접 실행해 보실 수 있습니다.
const N = 1000000;
// 1. new Array(N).fill(v)
console.time('new Array().fill()');
const arr1 = new Array(N).fill(0);
console.timeEnd('new Array().fill()');
// 2. Array.from({ length: N })
console.time('Array.from()');
const arr2 = Array.from({ length: N }, () => 0);
console.timeEnd('Array.from()');
// 3. Spread Operator (비교용)
console.time('[...Array(N)]');
const arr3 = [...Array(N)].map(() => 0);
console.timeEnd('[...Array(N)]');
📊 테스트 결과 (평균치)
| 방식 | 소요 시간 (ms) | 성능 특징 |
|---|---|---|
new Array(N).fill(0) | ~2.5ms | 가장 빠름. 엔진 수준에서 메모리를 통째로 할당하고 값을 채움. |
Array.from({ length: N }, callback) | ~15.0ms | 보통. 내부적으로 반복문을 돌며 콜백 함수를 호출하는 오버헤드 발생. |
[...Array(N)].map(callback) | ~45.0ms | 가장 느림. 이터러블 순회와 전개 연산자 오버헤드가 중첩됨. |
🧐 왜 이런 차이가 발생할까?
- Low-level Optimization:
new Array(n).fill(v)는 자바스크립트 엔진이 내부적으로 최적화된 C++ 루틴을 사용하여 메모리를 한꺼번에 조작합니다. 반면,Array.from()은 각 요소마다 사용자 정의 콜백 함수를 실행해야 하므로 컨텍스트 스위칭 비용이 발생합니다. - Packed vs Holey:
new Array(n)만 호출했을 때는 'Holey' 상태지만,.fill()이 호출되는 순간 엔진은 이를 'Packed' 상태로 인지하고 밀집 배열 최적화를 수행합니다. - Spread의 비용:
[...Array(N)]은 먼저 이터레이터를 생성하고, 그 값을 하나씩 펼쳐서 새로운 배열에 담는 과정을 거치기 때문에 가장 비효율적입니다.
🎯 결론: 무엇을 선택해야 할까?
- 성능이 최우선인 경우 (Big Data): 데이터가 수십만 개 이상이라면 망설임 없이 **
new Array(n).fill(v)**를 사용하세요. - 가독성과 편의성이 우선인 경우 (General): 데이터 양이 적고 인덱스를 활용한 복잡한 초기화가 필요하다면 **
Array.from()**이 훨씬 우아한 코드를 만들어 줍니다. - 유사 배열을 다룰 때: 성능 차이를 고려하더라도 **
Array.from()**이 표준이며 가장 안전한 선택입니다.
🎯 최종 요약
- **new Array(n)**은 값이 비어있는 희소 배열을 만든다. 고차 함수를 쓰려면
.fill()이 필수다. - **Array.from()**은 밀집 배열을 만들며, 매핑 능력이 강력하여 실무에서 가장 범용적으로 쓰인다.
- 성능이 최우선이라면
fill()을, 가독성과 기능이 중요하다면Array.from()을 선택하자.
참고: new Array()의 모호성 때문에 최신 스타일 가이드에서는 배열 리터럴([])이나 Array.of(), Array.from() 사용을 권장하고 있습니다.
excerpt: "단순히 배열을 만드는 것을 넘어, 메모리 구조와 엔진 최적화 관점에서 new Array()와 Array.from()의 차이점을 심도 있게 분석합니다."