ES6 이전에 기존의 반복문
for
,forEach
,for-in
들은 객체를 순회할 때 많은 문제점이 있었다. (제어문 - 조건문과 반복문)
const array = [1, 2, 3];
for (let i = 0; i < array.length; i++) {
const element = array[i];
console.log(element); // 1, 2, 3
}
const color = ["red", "pink"];
color.forEach(function (key) {
console.log(`내가 좋아하는 색깔은 ${key}입니다!`);
// 내가 좋아하는 색깔은 red입니다!
// 내가 좋아하는 색깔은 pink입니다!
});
const school = {
school_name: "인삼대학교",
student_number: "3000명",
};
for (const key in school) {
console.log(`${key}의 속성값은 ${school[key]} 입니다.`);
// school_name의 속성값은 인삼대학교 입니다.
//student_number의 속성값은 3000명 입니다.
}
따라서 ES6에서는 이러한 문제점을 해결하기 위해
for-of
라는 순회 방법을 도입하였다.
const array = [1, 2, 3];
for (const element of array) {
console.log(element); // 1, 2, 3
}
for-of 반복문을 통해 객체를 순회할 수 있는 이유는 이터레이션 프로토콜(=이터레이션 인터페이스) 때문이다.
순회 가능한 자료구조를 만들기 위한 프로토콜을 말하며 ES6에서 도입되었다.
ES6 이전의 순회 가능한 자료구조들은 통일된 규약 없이 각자 나름의 구조를 가지고 다양한 방법으로 순회하였다.
ES6 에서는 순회 가능한 자료구조를 이터러블로 통일하여 배열, 문자열 등과 같은 이터러블 객체를 일관된 방식으로 순회할 수 있도록 하였다.
이터레이션 프로토콜은 두 가지 주요한 요소로 구성된다.
이터러블 객체는 이터레이터를 리턴하는
[Symbol.iterator]()
메서드를 가진 객체이다. (설명아래)
for-of
역시 이터러블을 활용한 것이다. for-of
외에도 Spread 연산자도 이터러블을 활용할 수 있다.이터레이터란 이터러블 객체의 요소를 순회하기 위한 객체를 말한다. (반복 가능한 객체)
for of
구문을 사용하기 위해선 컬렉션 객체가 [Symbol.iterator]
속성을 가지고 있어야한다. (직접 명시 가능)배열한테 이터러블 표식을 없애보자
let arr = [1, 2, 3];
for (const a of arr) console.log(a); // 1,2,3
arr[Symbol.iterator] = null;
for (const a of arr) console.log(a); // TypeError: arr is not iterable
Symbol.iterator
를 null로 설정하면, 배열은 이터레이터를 생성할 수 없으므로 "TypeError: arr is not iterable"과 같은 에러가 발생한다.
Symbol.iterator
(이터레이터 심벌)는 이터레이터를 반환하는 메서드이다.
이터레이터 심볼이 없는 객체는 for-of
과 같은 이터레이터 기반의 반복 구문에서 사용할 수 없다.
이터레이터는 아래 항목을 만족시키는 객체이다.
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
printArr = iter.next();
console.log(printArr); // { value: 1, done: false }
for-of 문 없이 이터레이터를 반복실행
let a = [1, 2, 3];
let iter = a[Symbol.iterator](); // 변수 iter에 이터레이터 대입
while (true) {
let iteratorResult = iter.next();
if (iteratorResult.done === true) break;
let v = iteratorResult.value;
console.log(v); // 1, 2, 3
}
for-of문 사용
let a = [1, 2, 3];
for (let v of a) console.log(v); // 1, 2, 3
}
이처럼
for-of
문 역시Symbol.iterator
메서드를 가졌기 때문에 이터러블(반복가능)한 객체라고 할 수 있다.
이터러블과 이터레이터의 관계는 다음과 같다.
이터레이터는 배열의 부분 집합이다!
이터레이터는 next() 메서드를 통해 이전에 얻어온 값 바로 다음 값만 얻어올 수 있지만, 배열은 인덱스를 가지고 어느 곳에 있는 값이든 아무때나 가져오는게 가능하다. 또한 배열의 아이템의 총 개수도 바로 알 수 있다.
배열은 언제든 이터레이터로 변환할 수 있다.
하지만, 이터레이터를 배열로 전환하려면 next를 모두 불러 이터레이터 순회를 다 끝내야 가능하다.
//배열
const arr = [1, 2, 3];
// arr[0] == 1, arr[2] == 3
// arr.lenhth == 3
console.log(arr[0]); // 1
console.log(arr[1]); // 2
console.log(arr[2]); // 3
//이터레이터
const iterator = (function () {
let num = 1;
return {
next: function () {
return num > 3 ? { done: true } : { done: false, value: num++ };
},
};
})();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
이터레이터는 다음 아이템을 가져오는 것 외에 쓸 데가 없어보인다.
이터레이터가 배열의 부분집합이라면 배열만 사용하면 되는 것이 아닐까?
이터레이터는 배열보다 메모리 소모가 적다.
기능이 많은 것이 항상 좋은 것이 아니다. 기능이 많은 배열은 무거울 수 밖에 없다.
또한 배열을 이용하려면 모든 아이템들을 메모리에 올려놓아야하지만, 이터레이터는 코드 부분을 제외하면 변수 하나만 사용하면 된다.
이터레이터는 외부 데이터를 다루기 위한 수단으로도 많이 사용된다.
이미지 분석을 위해 특정 디렉토리에 있는 이미지를 불러와서 미리 작성해둔 코드를 돌려가며 처리를 한다고 생각해보자. 배열을 사용하면 모든 이미지를 미리 불러다 놓아야 하기 때문에 메모리를 많이 소모할 것이다. 하지만, 이터레이터를 사용한다면 현재 다루고 있는 이미지 하나만 메모리에 올리면 된다.
이터레이터를 직접 만들어서 구현해야할까?
아니다! 제너레이터(generator) 함수를 이용하면 편리하게 이너레이터를 만들 수 있다!