들어가며..
안녕하세요, Ocean입니다.
오늘은 평소 잘 모르던 개념인
자바스크립트의 이터러블(Iterable)과 이터레이터(Iterator)를 확실하게 정리해 보겠습니다.
참고 글
1. 이터러블이란?
이터러블(iterable, 반복 가능한) 객체는, 이름 그대로 순회 가능한 객체를 말합니다.
데이터를 순회하는 일은 매우 흔한 작업이며, 우리가 잘 알고 있는 배열은 순회하기에 적합한 구조를 가지고 있습니다.
자바스크립트에서는 어떤 객체가 특정한 조건을 가지면 배열이 아니더라도 배열처럼 순회할 수 있습니다.
이 때, 이 특정한 조건을 공통된 인터페이스로 추상화하여 객체에 적용하면 해당 객체를 이터러블 객체라고 부를 수 있게 됩니다.
이터러블 객체의 조건
- `Symbol.iterator`라는 메서드를 가지고 있어야 합니다.
- 이 `Symbol.iterator`는 이터레이터 객체를 반환해야 합니다.
- 이터레이터 객체는 next 메서드를 가지고 있어야 하며, 이 메서드는 { done: Boolean, value: any } 형식의 객체를 반환해야 합니다.
이 조건을 모두 만족하면 해당 객체는 이터러블, 즉 반복 가능한 객체가 됩니다.
자바스크립트에서 이터러블 객체는 for..of문으로 순회가 가능하며 전개 연산자를 사용할 수 있게 됩니다.
정리하자면,
이터러블: [Symbol.iterator]라는 메서드를 가지고 있는 객체, 이 때 Symbol.iterator는 반드시 이터레이터를 반환해야 한다.
이터레이터: next 메서드를 포함하고 있는 객체, next 메서드는 { done: Boolean, value: any }를 반드시 반환값으로 가져야 한다.
2. 이터러블 구현
실제 구현 코드
let iAmIterableObj = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of iAmIterableObj) {
alert(num); // 1, then 2, 3, 4, 5
}
위의 iAmIterableObj 객체는 이터러블 객체입니다.
- [Symbol.iterator] 메서드를 가지고 있습니다. [Symbol.iterator]는 자기 자신(this)를 반환합니다.
- 자기 자신, 즉 this 내부에는 next 메서드를 가지고 있습니다.
- next 메서드는 { done: Boolean, value: any }을 반환합니다.
이터러블의 조건을 모두 만족하는 객체입니다. iAmIterableObj는 for..of와 전개 연산자를 모두 사용할 수 있습니다!
하지만 위 객체는 조금 독특한 특징이 있습니다.
이터러블은 이터레이터 메서드를 [Symbol.iterator]라는 키 값으로 가지고 있는 것을 말합니다.
또한 이터레이터 메서드는 next 메서드를 포함하고 있습니다.
위의 iAmIterableObj 객체는 [Symbol.iterator]라는 키 값으로 함수를 가지고 있으면서 next 메서드 또한 가지고 있습니다.
즉 iAmIterableObj은 이터러블하면서 이터레이터입니다.
이러한 경우에, 동시에 여러 개의 for..of를 실행하면 상태가 공유됩니다.
for (let num of iAmIterableObj) {
console.log("first", num);
break; // current가 2가 됨
}
for (let num of iAmIterableObj) {
console.log("second", num); // 2부터 시작하게 됨
}
- iAmIterableObj 객체는 이터러블인 동시에 이터레이터 객체 그 자체
- next()의 상태 current는 iAmIterableObj 객체 내부에 직접 저장
- 한 번 루프를 돌고 break 하더라도 current++는 이미 실행되어 값이 2가 됨
- for..of를 다시 돌려도 새로운 상태를 만들지 않고, 이미 실행된 이터레이터의 current 값을 가져와서 이터레이터가 초기화되지 않음.
이 문제는 이터러블 객체와 이터레이터를 분리하면 해결할 수 있습니다.
이터러블과 이터레이터를 분리한 코드
let iAmIterable = {
from: 1,
to: 5
};
iAmIterable[Symbol.iterator] = function() {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
- iAmIterable 객체는 [Symbol.iterator] 메서드를 가지고 있음.
- 이 메서드는 current, last, next()를 포함한 이터레이터 객체를 반환
- for..of를 만날 때 마다 [Symbol.iterator]를 호출하고 호출될 때 마다 새로운 이터레이터 객체를 생성합니다. 따라서 for..of를 여러 번 호출해도 서로 영향을 주지 않습니다.
위 개념은 관심사의 분리, 즉 SOC와 연관이 있습니다.
iAmIterable 내부에는 next 메서드가 없고, 대신 iAmIterable[Symbol.iterator]를 호출해서 만든 이터레이터 객체와 해당 객체의 메서드의 next 메서드로 반복에 사용될 값을 만들어 냅니다.
이렇게 하면 이터레이터 객체와 반복 대상인 객체를 분리할 수 있습니다.
위와 같이 이터러블은 순회 대상인 객체와 순회 로직을 수행하는 이터레이터 객체를 구분함으로써, 각자의 역할을 분리할 수 있습니다.
3. 이터러블의 종류
이터러블의 종류는 다음과 같습니다.
타입 | 설명 |
Array | 배열은 대표적인 이터러블 객체 |
String | 문자열은 문자 단위로 순회가 가능한 이터러블 객체 |
Map | 키-값 쌍을 순회할 수 있다 |
Set | 중복 없는 값들의 집합, 각 값 순회 가능 |
TypedArray(ex: Uint8Array, Float32Array) | 이진 데이터 배열도 이터러블 |
arguments 객체 | 함수 내의 유사 배열이지만 이터러블합니다. |
DOM 컬렉션(ex: NodeList) | 브라우저에서 반환되는 일부 DOM 컬렉션도 이터러블 |
Generator | 이터레이터이자 이터러블 |
이 중 대표적인 문자열 이터레이터를 명시적으로 호출해보겠습니다.
let str = "Hello";
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // 글자가 하나씩 출력됩니다.
}
또한 이터러블인지 확인하려면 다음과 같은 코드를 사용해볼 수 있습니다.
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
isIterable([]); // true
isIterable("hello"); // true
isIterable(new Map()); // true
isIterable({}); // false
4. 이터러블 객체와 유사 배열 객체
이터러블과 비슷해 보이지만 다른 개념인 유사 배열 객체라는 개념이 있습니다.
유사 배열 객체는 다음과 같은 특징을 가집니다.
- 배열처럼 인덱스를 가지고 있다.
- 배열처럼 length 프로퍼티를 가지고 있다.
let arrayLike = { // 인덱스와 length프로퍼티가 있음 => 유사 배열 객체
0: "Hello",
1: "World",
length: 2
};
for (let i = 0; i < arrayLike.length; i++){
console.log(arrayLike[i]);
}
for (let item of arrayLike) {} // 유사 배열은 Symbol.iterator가 없으므로 에러 발생
특별한 기능은 없고, 위와 같이 length와 index를 통해 배열처럼 사용할 수 있는 것을 유사 배열 객체라고 합니다.
유사 배열 객체는 말 그대로 배열과 유사한 객체이기 때문에 배열처럼 push, pop 메서드는 사용할 수 없습니다.
5. Array.from
범용 메서드인 Array.from은 이터러블 객체 혹은 유사 배열 객체를 받아 진짜 Array 형태로 만들어 줍니다.
// 유사 배열 객체
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (메서드가 제대로 동작합니다.)
// 이터러블 객체
let iAmIterable = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
let last = this.to;
return {
next() {
if (current <= last) {
return { done: false, value: current++ };
} else {
return { done: true };
}
}
};
}
};
// Array.from은 이터러블(이터레이터 반환 객체)도 배열로 바꿔줌
let arr = Array.from(iAmIterable);
console.log(arr); // [1, 2, 3, 4, 5]
요약
이터러블: [Symbol.iterator]라는 메서드를 가지고 있는 객체, 이 때 Symbol.iterator는 반드시 이터레이터를 반환해야 한다.
이터레이터: next 메서드를 포함하고 있는 객체, next 메서드는 { done: Boolean, value: any }를 반드시 반환값으로 가져야 한다.
유사 배열 객체: 배열처럼 index, 0 부터 시작하는 정수를 키로 가지면서, length 프로퍼티가 있는 것
이터러블과 유사 배열 객체는 Array.from을 통해서 배열 형태로 변환할 수 있다.
'WEB > JAVASCRIPT' 카테고리의 다른 글
자바스크립트의 모듈 시스템 (CommonJS, ES Module) (0) | 2025.01.12 |
---|---|
이벤트 캡쳐링, 이벤트 버블링 (0) | 2024.02.13 |
Promise, 프로미스 (0) | 2024.02.03 |
Rest 파라미터, 스프레드 문법 (0) | 2023.04.10 |
클로저 (0) | 2023.02.15 |