NodeList 유사 배열 객체

NodeList가 왜 유사 배열 객체로 설계되었는지, 어떻게 순회가 가능한지 알아보자.

NodeList 유사 배열 객체인 이유와 순회 가능한 이유

웹 개발을 하다 보면 DOM 요소를 다룰 때 NodeList를 자주 접하게 됩니다. 평소에 단순히 배열처럼 사용했지만, 사실 NodeList는 배열이 아니라는 점에서 궁금증이 생겼습니다. 특히 for...of 문으로 순회가 가능한 이유를 알게 되면서, 왜 NodeList가 유사 배열로 설계되었는지도 학습하게 되었습니다. 이번 글은 제가 알게 된 내용을 바탕으로 정리한 기록입니다.


NodeList란 무엇인가?

1. NodeList의 정의

NodeList는 DOM API가 반환하는 유사 배열 객체입니다. 예를 들어, document.querySelectorAll('div')는 문서 내의 모든 <div> 요소를 NodeList 형태로 반환합니다. 배열처럼 보이지만 배열과는 차이가 있습니다.

2. NodeList의 특징

  • 유사 배열 객체: length 속성과 인덱스로 요소 접근 가능. 그러나 배열 메서드(map, filter 등)는 미지원.
  • 읽기 전용: DOM 요소를 추가하거나 제거할 수 없음.
  • 이터러블 객체: ES6 이후 이터러블 프로토콜을 구현해 for...of, 스프레드 연산자, Array.from 등으로 활용 가능.

왜 NodeList는 유사 배열로 설계되었을까?

NodeList가 배열이 아닌 유사 배열로 설계된 이유를 알아보면서, DOM 설계 철학과 성능 최적화가 주요 배경이라는 점을 알게 되었습니다.

1. 성능 최적화를 위한 설계

NodeList는 DOM 요소를 다루기 위한 가벼운 데이터 구조입니다. 배열은 더 많은 기능을 제공하지만, 이는 DOM 작업에서 불필요한 성능 비용을 유발할 수 있습니다. NodeList단순히 읽기와 순회 작업에 초점을 맞춘 구조로 설계되었습니다.

2. DOM 설계 철학

NodeList는 W3C DOM Level 1 표준(1998년) 시점에서 만들어졌습니다. 당시에는 고급 배열 메서드가 필요하지 않았으며, DOM API가 제공하는 기능에 초점을 맞추어 단순한 데이터 구조가 적합했습니다.

3. 읽기 전용 특성과 안전성

NodeList는 DOM 트리를 반영하며, 동적으로 변할 수 있는 읽기 전용 특성을 가집니다. 이를 통해 DOM 요소를 안전하게 다룰 수 있도록 설계되었습니다.

4. 과거와의 호환성 유지

NodeList를 배열로 변경하면 기존 코드와의 호환성이 깨질 위험이 있습니다. 따라서 NodeList는 기본 구조를 유지하면서 ES6 이후에 이터러블 프로토콜을 추가하는 방식으로 확장되었습니다.


NodeList가 for-of로 순회 가능한 이유

NodeList는 배열이 아니지만, 이터러블 프로토콜을 구현했기 때문에 for...of 문으로 순회가 가능합니다. 이 과정에서 자바스크립트의 이터러블과 Iterator 객체에 대해 학습하게 되었습니다.

1. 이터러블 프로토콜이란?

이터러블 프로토콜은 객체가 반복문(for...of)에서 순회 가능하도록 정의한 자바스크립트 표준입니다. 이터러블 객체가 되기 위해서는 다음 조건을 충족해야 합니다

  • Symbol.iterator 메서드를 구현해야 함.
  • 이 메서드는 Iterator 객체를 반환해야 함.

2. Iterator 객체란?

Iterator 객체는 next() 메서드를 제공하며, 호출 시 { value, done } 형태의 객체를 반환합니다:

  • value: 현재 값을 나타냅니다.
  • done: 순회가 끝났는지를 나타냅니다.

3. NodeList의 Symbol.iterator

NodeListSymbol.iterator 메서드를 구현하고 있습니다. 이를 통해 NodeList는 순회 가능한 Iterator 객체를 반환하며, DOM 노드를 차례대로 제공합니다.

4. 동작 원리 예제

아래는 NodeListSymbol.iterator를 통해 순회 가능한 이유를 보여주는 예제입니다.

const nodeList = document.querySelectorAll('div');
const iterator = nodeList[Symbol.iterator]();
 
console.log(iterator.next()); // { value: <div>, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

NodeList 활용 방법

NodeList를 사용할 때 배열 메서드가 지원되지 않아 직접 순회하거나, 배열로 변환하는 방법이 필요합니다.

1. for...of 문

for...of 문을 통해 NodeList를 손쉽게 순회할 수 있습니다.

const nodeList = document.querySelectorAll('div');
 
for (const node of nodeList) {
  console.log(node); // 각 div 요소 출력
}

2. 스프레드 연산자

NodeList를 배열로 변환하지 않고도 스프레드 연산자로 간편히 활용할 수 있습니다.

const nodeList = document.querySelectorAll('div');
const array = [...nodeList];
 
console.log(array); // 배열로 변환된 요소 출력

3. Array.from 메서드

명시적으로 NodeList를 배열로 변환하여 배열 메서드를 사용할 수 있습니다.

const nodeList = document.querySelectorAll('div');
const array = Array.from(nodeList);
 
array.map((node) => console.log(node));

NodeList와 배열의 차이

NodeList와 배열의 차이를 이해하면, 왜 NodeList가 배열처럼 동작하지 않는지 명확히 알 수 있었습니다.

NodeList와 배열 비교표

특징NodeList배열 (Array)
구조유사 배열 객체배열 객체
순회 가능 여부이터러블 (for...of 가능)이터러블 (for...of 가능)
메서드 지원배열 메서드 미지원map, filter 등 사용 가능
변경 가능 여부읽기 전용요소 추가/제거 가능

요약 및 정리

NodeList는 배열처럼 보이지만, DOM 설계 철학과 성능 최적화를 위해 유사 배열 객체로 설계되었습니다. ES6 이후에는 이터러블 프로토콜을 통해 for...of 문으로 순회가 가능해졌으며, 필요한 경우 배열로 변환해 고급 메서드도 사용할 수 있습니다. 이를 이해하면서 DOM API와 자바스크립트의 기본 동작 원리를 더 깊이 알게 되었습니다.


느낀점

이번에 NodeList를 학습하면서 단순히 "왜 배열이 아닌가?"라는 질문에서 시작해, DOM 설계 철학과 자바스크립트의 이터러블 동작 원리까지 이해할 수 있었습니다. 특히 Symbol.iteratorIterator 객체와 같은 개념이 실제로 어떻게 동작하는지를 알게 되어, 앞으로 코드를 작성할 때 더 깊이 있는 접근이 가능할 것 같습니다. NodeList라는 개념도 이렇게 학습해 보니 새로운 시각을 갖게 되었고, DOM API의 설계 의도를 이해하게 된 점이 의미 있었습니다.


참고