티스토리 뷰
ES6에서 이터레이션 프로토콜이라는게 도입되었다.
이전에는 순회 가능한 데이터들
예를 들면 배열이나, 문자열 같은 객체들은 통일된 규약 없이 나름의 개별적인 구조로 각자 다양한 방법으로 값을 순회할 수 있었다.
하지만 ES6에 이터레이션 프로토콜이 등장하면서
순회가능한 객체들을 iterable한 객체라고 하며 해당 객체들만이 누릴 수 있는
순회와 관련된 문법(스프레드, 디스트럭처링 ...)이 사용될 수 있도록 통합되어졌다.
Iterable
사실 파이썬 iterable, iterator와 관련해서 필자가 포스팅을 작성한 적이 있는데
사실 JS도 똑같은 개념이다.
iterable, iterator 단어의 뜻 그대로 각각 "반복할 수 있는", "반복자"이며
파이썬에서는 해당 객체에 __iter__ 매직 메서드가 존재하면 iterable한 객체였지만
JS에서는 Symbol.iterator() 함수가 구현되어 있으면 iterable한 객체다.
파이썬이든 JS든 사실 메서드 이름만 다를 뿐이지 동작은 거의 똑같다고 보면 된다.
핵심은 파이썬의 __iter__매직 메서드든 JS의 Symbol.iterator 함수 든 둘 다
해당 객체의 iterator객체를 만들어 낸다는 점이다.
결국 iterator("반복자")를 만들어 낼 수 있는 객체(__iter__, Symbol.iterator가 구현된)는
iterable("반복할 수 있는")한 객체가 되는 것이다.
실제로 필자가 JS에서 빌트인 iterable 타입인 배열에
Symbol.iterator() 함수를 사용하여 해당 배열 객체의 iterator를 만들어 보고
iterator의 next() 메서드를 호출하여 리턴값을 살펴보니
var arr = [1, 2, 3, 4, 5];
var iterator = arr[Symbol.iterator]();
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: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
iterable한 배열 객체의 값들을 순회하며
생성한 iterator가 순회를 마쳤는지의 여부를 boolean값으로 표시한다.
(※ 마지막을 보면 arr배열의 마지막 인덱스 값인 5까지 순회한뒤 다음 next() 메서드의 반환값은 value: undefined, done: true이다.)
※ 여담으로 한 가지 재밌는 사실을 말하면
파이썬도 똑같이 iterator의 __next__매직 메서드를 통해 iterator를 순회하며
마지막 끝 지점에 도착하고 다시 next를 호출하면 에러가 발생한다.
JS에서 iterable한 객체는 for ... of 문을 통해 해당 객체의 값을 반복할 수 있는데
iterator를 생성하고 next를 메서드를 사용하여 iterable한 객체의 값을 순회하는 과정을 담은 위 예제를 봤다면
for ... of 반복문의 내부 동작도 이해할 수 있다.
var arr = [1, 2, 3, 4, 5];
// 반복문 시작전에 먼저 반복할 객체의 iterator를 생성
var iterator = arr[Symbol.iterator]();
for (;;) {
// 이터레이터의 다음값을 가져옴
const current = iterator.next();
// 만약 다음값의 done 프로퍼티가 true일 경우 이터레이터가 전부 다 순회했다는 뜻 --> 반복문 종료
if (current.done) {
break;
};
// 현재 차례의 값을 반환
return current.value;
};
위 예시처럼 for ... of문은 반복하려고 하는 객체의 반복자(iterator)를 먼저 생성하고
해당 반복자(iterator)의 next() 메서드를 호출하여 반복자(iterator)의 값을 순회한다.
그렇다면 만약 iterable하지 않은 객체를 for ... of문으로 값을 순회하려 한다면 어떻게 될까??
var objectA = {
a: 1,
y: 2
}
for (key of objectA) {
console.log(key);
}
// TypeError: objectA is not iterable
일반객체를 for ... of문에 사용해 봤더니
해당 객체는 iterable하지 않다고 타입에러가 발생한다.
이렇게 for ... of는 iterable한 객체만 순회할 수 있다.
※ 그나저나 JS에는 for ... of 말고도 for ... in 반복문도 존재한다.
for ... in같은 경우는 해당 객체의 속성 중 enumerable이 true일 경우 반복이 가능하다.
일반 객체 같은 경우는 enumerable이 true이기 때문에 아래 예제처럼
for ... in 반복문에서는 정상적으로 잘 동작한다.
var objectA = {
a: 1,
y: 2
};
console.log(Object.getOwnPropertyDescriptors(objectA));
// {
// a: { value: 1, writable: true, enumerable: true, configurable: true },
// y: { value: 2, writable: true, enumerable: true, configurable: true }
// }
for (value in objectA) {
console.log(value);
};
// a
// y
이렇게만 보면 객체를 순회한다는 점에서 for ... in 이나 for ... of 나 같은거 같지만
for ... in 같은 경우는 객체의 키를 반복한다는 것이다.
그래서 for ... in을 배열객체에 사용해 보면
var array = ["hello", "world", "!"]
for (value in array) {
console.log(value); // 1 2 3
};
['hello', 'world', '!'] 배열 객체의 값을 순회하는 게 아닌 키 값 1, 2, 3을 순회한다.
( ★ JS 배열은 Object를 상속받아 객체이며 키 값으로 인덱스와 length를 가지고 있다 ★ )
array = ["hello", "world", "!"];
console.log(Object.getOwnPropertyDescriptors(array));
// 출력결과
{
'0': {
value: 'hello',
writable: true,
enumerable: true,
configurable: true
},
'1': {
value: 'world',
writable: true,
enumerable: true,
configurable: true
},
'2': { value: '!', writable: true, enumerable: true, configurable: true },
length: { value: 3, writable: true, enumerable: false, configurable: false }
}
이런 특징 때문에 JS에서 배열은 일반적인 배열이 아닌 객체의 형태로 배열을 흉내 낸 희소 배열이라고 한다.
(파이썬의 dict를 배열의 형태로 만든 것과 같다.)
빌트인 Iterable 객체
JS에서 빌트인 iterable 객체는 Array, String, Map, Set, ByteArray, DOM 등등이 있다.
실제로 해당 타입의 객체들이 iterable인지 확인해 보겠다.
iterable인지 확인하는 법은 간단하다.
필자가 위에서 iterable이 되려면 iterator를 만들 수 있는 Symbol.iterator()메서드가 구현되어 있어야 한다고 했다.
(※ 프로토타입 체인을 통해 Symbol.iterator()메서드를 상속받는 것도 똑같이 Symbol.iterator()가 구현되어 있다고 할 수 있다.)
그렇기 때문에 해당 객체가 Symbol.iterator()메서드를 가지고 있는지 확인만 하면 된다.
var objectA = {
a: 1,
y: 2
};
var arrayA = [1, 2, 3, 4];
var mapA = new Map([
['a1', 1],
['b1', 2]
]);
var setA = new Set([1, 3, 5]);
console.log(Symbol.iterator in objectA); // false
console.log(Symbol.iterator in arrayA); // true
console.log(Symbol.iterator in mapA); // true
console.log(Symbol.iterator in setA); // true
테스트에 사용한 객체의 타입은 일반객체, 배열, Map, Set이다.
마지막 Symbol.iterator출력결과를 확인해 보면 objectA를 제외한 나머지 타입들은 true인걸 확인할 수 있다.
Iterable한 객체로 만들어보자!
그렇다면 일반 객체는 iterable이 될 수 없을까??
일반 객체도 iterable이 될 수 있다.
우리는 이미 iterable이 될 수 있는 조건을 알고 있지 않은가?
일반 객체(사용자 정의)에 Symbol.iterator()함수를 구현하면 iterable이 되지 않을까?
필자는 iterObj 일반객체에 Symbol.iterator함수를 구현하였다.
해당 iterator 함수는 iterObj의 value값들을 배열로 만든 뒤(iterator 생성)
iterator의 next메서드로 해당 배열의 값을 순회할 수 있도록 했다.
(★ 당연한 이야기지만 next메서드 또한 꼭 구현해야 한다. iterable의 필수조건이 Symbol.iterator를 구현하는 것이라면
iterator가 되기 위한 필수조건은 당연히 해당 객체를 순회할 수 있도록 next메서드가 필요하다. ★)
var iterObj = {
a: 1,
b: 2,
c: 3,
d: 4,
[Symbol.iterator]() {
// [1, 2, 3, 4]
let valueList = Object.keys(this).map((key) => this[key]);
let index = -1;
return {
next() {
index++;
return { value: valueList[index], done: index >= valueList.length}
}
}
}
}
console.log(Symbol.iterator in iterObj); // true
var iterator = iterObj[Symbol.iterator]();
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: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
for (value of iterObj) {
console.log(value); // 1 2 3 4
}
마지막 출력결과를 확인하자
필자가 만든 일반 객체에 iterator를 생성하고 next()메서드를 사용하여 해당 iterator를 순회하였고
for ... of 문에서도 잘 작동하는 걸 확인할 수 있다.
이렇게 JS에서의 iterable, iterator에 대해 알아봤는데
이렇게 iterable한(순회 가능한)객체 들은 자신의 특성을 이용해서 디스트럭처링, 스프레드 문법을 사용할 수 있다.
이러한 부분들은 디스트럭처링, 스프레드에 대해 배울 때도 왜 이런 문법들이
가능한지에 대해 어느 정도 감을 잡을 수 있을뿐더러 내부 동작들에 대한 이해도도 높아지기 때문에
이러한 언어적인 배경지식을 터득하는 것은 굉장히 유용하다고 생각한다.
'HTTP, JS' 카테고리의 다른 글
[JS] 비동기 처리를 위한 Promise 객체 (0) | 2023.07.28 |
---|---|
[JS] 스프레드(spread) 문법 (0) | 2023.07.22 |
Alert를 커스터마이징 하기 위해 Modal 만들어보기 (0) | 2023.05.27 |
[WEB] 자바스크립트 이벤트 (0) | 2022.08.16 |
[WEB] DOM(Document Object Model) (0) | 2022.08.09 |