← 글 목록

notes

27장. 배열

모던 JS Deep Dive — 배열 생성·요소 접근·고차 메서드 핵심 정리.

참고: 이웅모 《모던 자바스크립트 Deep Dive》 27장 · [MDN Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)

JS 배열은 진짜 밀집 배열이 아니라 객체 + length에 가깝다. 인덱스는 문자열 키처럼 취급되고, 중간이 비면 hole이 생긴다. “배열답게” 쓰려면 메서드·hole·희소/밀집을 구분해야 한다.

flowchart LR
  A[Array] --> LEN[length]
  A --> IDX["0..n 인덱스 키"]
  A --> PROTO[Array.prototype 메서드]
  HOLE[희소 hole] --> SKIP[일부 메서드가 건너뜀]

27.1–27.2 배열이란 / JS 배열은 배열이 아니다

전형적인 자료구조 배열은 동일 크기 슬롯의 연속 메모리. JS 배열은 해시 친화적 객체에 length가 붙은 형태에 가깝다. 그래서 중간 삭제가 가능하고, 희소 배열이 생긴다.

const a = [1, 2, 3];
console.log(typeof a, Array.isArray(a)); // object true
a[100] = 1;
console.log(a.length); // 101 — hole 포함 길이

생성

const a = [1, 2];
const b = Array.of(1, 2);
const c = Array.from('hi'); // ['h','i']
const d = new Array(3); // 길이 3 희소 — 실수 많음
const e = Array(3).fill(0);

new Array(3) vs Array.of(3) — 전자는 길이, 후자는 원소 하나.

length

writable. 줄이면 뒤가 잘리고, 늘리면 hole.

const a = [1, 2, 3];
a.length = 1;
console.log(a); // [1]

희소 vs 밀집

hole은 in/forEach/map 등에서 다르게 취급된다. for…of는 hole을 undefined처럼 도는 편.

const a = [1, , 3];
a.forEach((v, i) => console.log(i, v)); // 0,1 / 2,3 — 1 건너뜀
for (const v of a) console.log(v); // 1, undefined, 3

요소 추가·삭제

  • push/pop — 끝
  • unshift/shift — 앞 (비쌈)
  • splice — 중간
  • delete a[i] — hole 남김. splice로 지우는 편이 낫다

고차 메서드

const nums = [1, 2, 3, 4];
console.log(nums.map((n) => n * 2));
console.log(nums.filter((n) => n % 2 === 0));
console.log(nums.reduce((a, b) => a + b, 0));
console.log(nums.find((n) => n > 2));
console.log(nums.some((n) => n > 3), nums.every((n) => n > 0));

순수하게 쓰려면 원본을 변이하지 않는 메서드를 고른다. sort/reverse/splice는 뮤테이션.

sort 함정

[10, 2, 1].sort(); // [1, 10, 2] — 문자열 비교 기본
[10, 2, 1].sort((a, b) => a - b); // [1, 2, 10]

유사 배열

arguments, NodeList 등 — length와 인덱스는 있으나 Array 메서드 없음 → Array.from / spread.

function f() {
  return Array.from(arguments);
}

예제

const users = [
  { id: 1, name: 'a' },
  { id: 2, name: 'b' },
];
const byId = Object.fromEntries(users.map((u) => [u.id, u]));
const names = users.map((u) => u.name).join(', ');
// 불변 push
const next = [...a, 4];
const without = a.filter((_, i) => i !== 1);

헷갈리던 장면 / 함정

  • new Array(n)으로 값 n 넣으려다 희소
  • sort 기본 비교
  • map이 hole을 건너뛰어 결과도 희소
  • const 배열 = 내용 불변이라고 착각(15)
  • sparse + JSON.stringify — hole이 null로

표

질문 → 자답

Q. 배열 vs 객체? A. 순서·length·배열 메서드가 필요하면 배열. 키-값 사전은 Map/객체.

Q. for…of vs forEach? A. for…of는 break/async 유리. forEach는 콜백, hole 스킵.

웹 보강 메모

MDN Array 메서드표의 mutating 열을 북마크. toSorted/toReversed(새 불변 메서드) 지원 환경을 확인.

실무로 바로

  • 생성: 리터럴 / from / of
  • 변환: map/filter/reduce
  • 정렬: 복사 후 compare
  • 유사 배열: Array.from
  • hole 만들지 않기

변이 vs 비변이 선택

상태를 스토어에 두면 변이 메서드가 구독 시스템을 깨뜨린다. 새 배열을 만들고 참조를 바꾼다.

const push = (arr, x) => [...arr, x];
const setAt = (arr, i, x) => arr.map((v, idx) => (idx === i ? x : v));
const removeAt = (arr, i) => arr.filter((_, idx) => idx !== i);

reduce로 그룹핑

const list = [
  { t: 'a', n: 1 },
  { t: 'b', n: 2 },
  { t: 'a', n: 3 },
];
const grouped = list.reduce((acc, cur) => {
  (acc[cur.t] ||= []).push(cur);
  return acc;
}, {});

Array.isArray의 위치

typeof [] === 'object'이므로 배열 판별은 Array.isArray. iframe/realm을 건너면 프로토타입이 달라질 수 있어 isArray가 안전장치.

copyWithin / fill

const a = [1, 2, 3, 4];
a.fill(0, 1, 3); // [1,0,0,4]
[1, 2, 3, 4].copyWithin(0, 2); // 변이 복사

저수준. 대부분 map/slice로 충분.

이터러블과의 연결

배열은 이터러블. spread, for…of, Array.from이 같은 규약 위에 있다(Iterator 노트).

const set = new Set([1, 2, 2]);
console.log([...set], Array.from(set));

큐·스택을 배열로

const q = [];
q.push(1); q.push(2);
q.shift(); // 큐 — 앞 제거 비용
const stack = [];
stack.push(1); stack.pop(); // 스택 — 끝만

대량 큐는 인덱스를 옮기는 deque 구현이 나을 수 있다.

at / with (모던)

const a = [1, 2, 3];
console.log(a.at(-1)); // 3
const b = a.with(1, 99); // 복사본 [1,99,3] — 지원 환경

음수 인덱스는 at. 불변 업데이트는 with/toSorted 계열.

정렬 안정성·비교자

모던 엔진 sort는 안정 정렬인 경우가 많다. 비교자는 음/0/양만 지키면 된다. 객체 배열:

users.sort((a, b) => a.name.localeCompare(b.name, 'ko'));

JSON과 배열

JSON.stringify는 hole을 null로. 복원 후 length는 유지될 수 있으나 의미가 달라진다. hole 금지 정책이 속 편하다.

여기서 잠깐 💁‍♂️

28–30은 Number·Math·Date. 배열 인덱스·length와 숫자 강제 변환이 맞물린다.

보강 — 다차원·flatten

const matrix = [[1, 2], [3, 4]];
console.log(matrix.flat());
console.log([[1, [2]]].flat(2));
console.log([1, 2, 3].flatMap((n) => [n, n * 2]));

보강 — 인덱스 검색

indexOf/includes는 동일 비교. NaN은 includes가 잡아주고 indexOf는 못 잡는 차이가 있다. 객체는 참조 비교 — find로 조건 검색.