책 읽다가 코딩하다 죽을래

자바스크립트 이터러블에 대해 본문

코딩/자바스크립트

자바스크립트 이터러블에 대해

ABlue 2024. 5. 13. 23:45

이 글은 자바스크립트 Deep Dive 이터러블 편을 읽고 개인적으로 정리한 글입니다.


이터러블이란 이터러블 프로토콜을 준수한 객체를 말한다.

말 그대로 프로토콜이니 원활한 통신을 위해 지키기로 약속한 규약이다. HTTP 프로토콜처럼 말이다.

그럼 이터러블은 무엇이고, 이걸 왜 지켜야하는지 알아보도록 하자.



📕 이터러블 프로토콜과 이터레이터 프로토콜

이터러블은 두 가지의 프로토콜을 준수하고 있는데 그것은 바로

이터러블 프로토콜이터레이터 프로토콜이다.

 


이터러블 프로토콜은 Well-Known Symbol인 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환하는 것을 말한다.

 

이터레이터 프로토콜은 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로로콜을 준수한 이터레이터를 반환한다. 이터레이터는 next 메서드를 갖고 있는 객체이다. next 메서드를 호출하면 이터러블을 순회하면 value와 done 프로퍼티를 갖는 이터레이터 리절트(result) 객체를 반환하는 것을 말한다. 이터레이터 프로토콜을 준수한 객체를 이터레이터라고 한다. 이터레이터는 마치 이터러블의 요소를 탐색하기 위한 포인터라고 생각하면 이해하기 쉽다.

 

❓그렇다면 왜 이터러블 프로토콜을 지켜야 할까요? 지켜서 얻는 이점이 무엇일까요?

 

이터러블 프로토콜을 준수한 객체는 for...of문으로 순회할 수 있으며 스프레트 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있기 때문이다.

 

const array = [1, 2, 3];

// for ... of 순회
for(const item of array){
	console.log(item);  // 1, 2, 3
}

// 스프레드 문법
console.log([...array]);  // [1, 2, 3]

// 배열 디스트럭처링 할당
const [a, ...rest] = array;
console.log(a, rest); // 1, [2, 3]

 

 

이터러블 프로토콜을 준수하지 않은 일반 객체는 for...of 문으로 순회할 수도, 스프레드 문법과 배열 디스트럭처링 할당의 대상으로도 사용할 수 없다.

 

// 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
const obj = { a: 1, b: 2 };

console.log(Symbol.iterator in obj); // false

// 이터러블이 아닌 일반 객체는 for ... of 로 순회할 수 없다.
for(const item of obj) {  // TypeError: obj is not iterable
	console.log(item);
}

// 이터러블이 아닌 객체는 배열 디스트럭처링 할당의 대상으로 사용할 수 없다.
const [a, b] = obj; // TypeError: obj is not iterable

// 하지만 일반 객체만 예외적으로 이터러블이 아닌데도 불구하고 스프레드 문법이 사용 가능하다.
// 스프레드 프로퍼티 제안(Stage 4)으로 인해 객체 리터럴 내부에서 스프레드 문법의 사용을 허용한다.
console.log({ ...obj }); // { a: 1, b: 2 }

 

 

📗  이터레이터

 

 

 

이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환하는데, 이러한 이터레이터는 next 메서드를 공통으로 갖고 있다. 이 next 메소드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할을 한다. next 메소드를 호출할 때마다 이터러블의 요소를 하나씩 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를 반환한다.

 

const array = [1, 2, 3];

// Symbol.iterator 메서드는 next 메소드를 갖는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();

// next 메소드를 호출하면 이터러블을 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를 반환한다.
console.log(iterator.next()); { value: 1, done: false }
console.log(iterator.next()); { value: 2, done: false }
console.log(iterator.next()); { value: 3, done: false }
console.log(iterator.next()); { value: undefined, done: true }

 

 

 

📘  이터러블의 종류 ( 빌트인 이터러블 )

 

그러면 이터러블 프로토콜을 준수하는 것들은 무엇이 있는지 알아보겠다.

다음 표준 빌트인 객체들은 이터러블 프로토콜을 준수하는 빌트인 이터러블이다.

 

빌트인 이터러블 Symbol.iterator 메소드
Array Array.prototype[Symbol.iterator]
String String.prototype[Symbol.iterator]
Map Map.prototype[Symbol.iterator]
Set Set.prototype[Symbol.iterator]
TypedArray TypedArray.prototype[Symbol.iterator]
arguments arguments[Symbol.iterator]
Dom 컬렉션 NodeList.prototype[Symbol.iterator]

HTMLCollection.prototype[Symbol.iterator]

 

 

📙 for ... of 문

for ... of 문은 이터러블을 순회하면서 이터러블의 요소를 변수에 할당하는 문이다.

for ... of 문의 문법은 다음과 같다.

 

for (변수선언문 of 이터러블) { 
	// do something
}

 

실제 사용 예시는 다음과 같다.

 

for (const element of [1, 2, 3]){
	// item 변수에 순차적으로 1, 2, 3이 할당된다.
	console.log(item); // 1 2 3
}

 

 

- for ... of 의 동작원리

 

for ... of 문은 내부적으로 이터레이터의 next 메서드를 호출하여 이터러블의 요소를 순회하며 next 메서드가 반환한 이터레이터의 리절트 객체의 value 프로퍼티 값을 for ... of 문의 변수에 할당한다. 이는 리절트 객체의 done 프로퍼티가 false일 때까지 반복된다.

 

앞에서 말한 것을 코드로 표현하면 다음과 같다.

 

const iterable = [1, 2, 3];

const iterator = iterable[Symbol.iterator]();

for( ;; ){
    const resultObject = iterator.next();
    if(resultObject.done) break;
    const element = res.value;
    console.log(element); // 1 2 3
}

 

 

 

📚 이터레이션 프로토콜의 필요성

 

이터러블이 없었을 때인 ES6 이전에는 순회 가능한 데이터 컬렉션(배열, 문자열, DOM 컬렉션 등)은 통일된 규약 없이 각자 나름이 구조를 가지고 for 문, for ... in 문, forEach 메소드 등으로 다양한 방법으로 순회할 수 있었다. 하지만 ES6가 나오고 이터러블이 등장하면서 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일하였고, 이런 이터러블은 for ... of, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 일원화했다.

이터러블은 for ... of 문, 스프레드 문법, 배열 디스트럭처링 할당과 같은 데이터 소비자에 의해 사용되므로 데이터 공급자의 역할을 한다.

 

만약 다양한 데이터 공급자가 각자의 순회 방식을 갖는다면 데이터 소비자는 이러한 데이터 공급자들에 의해 순회방식을 일일이 구현하고 지원해야 한다. 즉 미래에 컬렉션이 추가될 때마다 데이터 소비자는 기능이 변경되지 않았지만 변경사항이 생겨나는 것이다. 


하지만 순회 가능한 데이터 컬렉션들의 순회하는 방식을 이터러블 프로토콜을 따르게 만든다면 데이터 소비자는 이터레이션 프로토콜만 지원하도록 구현하면 된다. 즉 미래의 또다른 타입들의 데이터 컬렉션이 추가되어도 데이터 소비자는 변경사항이 없다. 이는 확장에는 열려있고 변경에는 닫혀있는 개방 폐쇄 원칙(OCP)을 모티브로 구성되어 있다.

 

 

이처럼 이터레이션 프로토콜은 다양한 데이터 공급자가 하나의 순회 방식을 갖도록 규정하여 데이터 소비자가 효율적으로 다양한 데이터 공급자를 사용할 수 있도록 데이터 소비자와 데이터 공급자를 연결하는 인터페이스의 역할을 한다.

 

 

 

 

 

✅ 정리

  • 이터러블 프로토콜 : Well-Known Symbol인 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환하는 것을 말한다.
  • 이터레이터 프로토콜 : 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로로콜을 준수한 이터레이터를 반환하는 것을 말한다.
  • 이터레이터 : next 메서드를 갖고 있는 객체이다. next 메서드를 호출하면 이터러블을 순회하면 value와 done 프로퍼티를 갖는 이터레이터 리절트(result) 객체를 반환한다. 이터레이터 프로토콜을 준수한 객체를 이터레이터라고 한다.