객체나 배열을 순회하는 메서드를 적용하고자 할 때, TypeError : X is not iterable과 같은 오류를 마주한 적이 있을 것이다. 정확히 무엇을 의미하는지, 어느 객체 유형에 알맞은 메서드인지 정리하려 한다.
Iteration
프로그래밍에서 Iteration이란 특정 작업을 반복적으로 수행하는 '행위'를 말한다. 데이터 구조의 각 요소를 순회하거나, 특정 작업을 반복 실행시킨다. 또한 Iteration이 가능한 객체 구조들을 Iterable(반복가능)하다고 말한다. 잠재적으로 반복이 가능함을 형용사로 부여하는 것이다.
자바스크립트의 배열(Array), 문자열(String), 집합(Set), 맵(Map)이 이에 해당한다. 객체(Object)는 반복이 불가능하고, 프로퍼티를 열거(enumeration) 형태로 순회한다는 차이점이 있다.
Iterable / Iterator
그럼 자바스크립트에서는 iterable한 객체 구조를 어떻게 정의하고 판단할까? 바로 객체 구조별 갖고 있는 프로토타입의 [Symbol.iterator]() 메서드를 갖고 있는지의 여부를 통해 이를 구분한다.
[Symbol.iterator] 메서드는 value(값)와 done(순회완료여부), 다음 값을 가진 객체를 리턴하는 next() 내부메서드로 구성된 객체를 반환한다. 이렇게 반환된 객체를 바로 Iterator라고 부르는 것이다. 프로퍼티와 메서드를 부여해 Iteration과 Iterator을 직접 구현할 수도 있다.
// 여기서 선언되는 문자열을 iterable하다고 한다.
var someString = new String("hi");
someString[Symbol.iterator] = function() {
// 여기서 반환되는 객체가 iterator이다.
return {
// 이렇게 다음 객체를 반환하는 next() 메서드를 갖고 있다.
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
배열을 예로 들어 정리해보자면, 우리가 선언한 배열은 Iterable한 객체이고, [Symbol.iterator]에서 처음 반환된 Iterator를 시작으로 내부에 있는 next() 메서드를 통해 다음 값으로 이동하며 순회하는 것이다.
반복 메서드
따라서 Iterable한 객체 구조인 Array, String, Set, Map은 전부 [Symbol.iterator] 프로토타입 메서드를 내장하고 있고 for, while, forEach, for...of 등의 반복 메서드를 이용해 Iteration을 구현할 수 있다.
아래는 몇 가지 예시이다.
1.
const str = "Hello";
for (const char of str) {
console.log(char);
}
// "H", "e", "l", "l", "o"
2.
const userMap = new Map([
["name", "John"],
["age", 30],
["city", "New York"],
]);
userMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// "name: John", "age: 30", "city: New York"
Enumeration
반복이 아닌 '열거'를 뜻한다. 처음 자바스크립트를 배울 때 Iteration과 꽤나 혼동되었던 개념이었지싶다. 어떻게 보면 반대되는 개념이라고 볼 수 있다. Enumeration은 객체(Object)의 속성(프로퍼티)를 순회하면서 각 항목에 대한 정보를 얻어온다. for...in 메서드를 통해 키 이름을 순회하고 값에 접근할 때 사용한다. 그래서 객체에서는 forEach를 사용할 수 없었던 것이었다.
아래와 같이 사용할 수 있다.
const person = {
name: "John",
age: 30,
city: "New York",
};
for (const key in person) {
console.log(`${key}: ${person[key]}`);
}
// "name: John", "age: 30", "city: New York"
Reference