본문 바로가기

Web[웹]/ES6+ 함수형 언어

[JS: ES6+] ES6에서의 순회와 이터러블:이터레이터 프로토콜

#1 기존과 달라진 ES6에서의 리스트 순회 ( for of, for i++)

 

//기존 리스트 순회
const list = [1, 2, 3];
for (var i = 0; i< list.length; i++) {
   console.log(list[i]); // 1 2 3
}

//기존 문자배열 순회
const str = 'abc';
for (var i = 0; i< str.length; i++){
   console.log(str[i]); // a b c
}

//바뀐 리스트 순회
for (const a of list) {
   console.log(a); // 1 2 3
}

//바뀐 문자배열 순회
for (const a of str) {
   console.log(a); // a b c
}

 


#2 Array, Set, Map을 통해  알아보는 이터러블/이터레이터 프로토콜

 

Array, Set, Map을 통해 알아보기

 

console.log('Arr -----------');
const arr = [1, 2, 3];
for (const a of arr) console.log(a); //1 2 3
// arr[0] ~ arr[2]를 통해 값을 조회 가능
console.log('Set -----------');
const set = new Set([1, 2, 3]);
for (const a of set) console.log(a); // 1 2 3
// set[0] ~ set[2]를 통해 값을 조회 불가능
console.log('Map -----------');
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map) console.log(a); // ['a', 1] ['b', 2] ['c', 3]
// map[0] ~ map[2]를 통해 값을 조회 불가능

 

set, map에서 set[x], map[x]로 값을 조회 못하는데, arr처럼 숫자로 된 키와 매핑된 값들을 순회하면서 동작하는 방식이 아님을 알 수 있다. 그런데도 for...of문을 통해 값이 순회하며 조회됨 을 볼 수 있는데, 이는 for...of문이 다른 방식으로 동작함을 확인할 수 있다

 


이터러블/이터레이터 프로토콜

 

이터러블 프로토콜은 ES6에서 도입된 것으로 순회(반복) 가능한 객체를 나나태는 프로토콜이라 할 수 있다.

for...of 반복문, ...전개 연산자, 구조 분해 등과 같이 함께 동작 할 수 있도록한 프로토콜이다. 즉, 이터러블 이여야만 이러한 동작이 가능하다.

 

-이터러블: 이터레이터를 리턴하는 [Symbol.iterator]()를 가진 값

-이터레이터: { value, done } 객체를 리턴하는 next()를 가진 값

-이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약

 

const arr = [1, 2, 3];

for(const n of arr) {
   console.log(n);
}
// 1 2 3

console.log(...arr);
// 1 2 3

const [first, ...remain] = arr;
console.log(first, remain);
// 1, [2, 3]

 

위에서 for...of 순회가 가능한 이유는 이터러블이기 때문인데 아래에서 더 확인해보자.

 

console.dir(arr);

/*
▼Array(3)
   ▼__proto__: Array(0)
      ...
      ▶ Symbol(Symbol.iterator): f values()
      ...
*/

 

Array의 내부에서 Symbol(Symbol.iterator): f values()를 확인할 수 있는데, Array에는 Symbol(Symbol.iterator)이라는 Key를 가진 함수가 존재한다는 것을 알 수 있고, 앞서 설명한 이터레이터를 반환하는 Symbol.iterator이라는 키값의 메소드를 가진 객체에 부합하는 것을 볼수 있다.

 

위의 코드를 아래와 같이 한번 해보자.

 

const arr = [1, 2, 3];

arr[Symbol.iterator] = null;

for(const n of arr) { 
  console.log(n);
}
// Uncaught TypeError: arr is not iterable

console.log(...arr);

 

arr[Symbol.iterator] = null; 을 통해 이제 더이상 배열 arr은 이터러블이 아니게 되었다. 그래서 for...of에서 arr은 이터러블이 아니라는 오류를 만나게 되었고, 전개 연산자의 경우도 마찬가지다.

 

이제 이터레이터를 확인해보자.

 

arr[Symbol.iterator]은 함수이므로, arr[Symbol.iterator]()를 한다면 반환되는 것은 이터레이터가 될 것 이다.

 

const arrIter = arr[Symbol.iterator]();

console.log(arrIter); // Array Iterator {}

 

위 코드를 통해 반환된 것이 이터레이터 객체인 것을 확인했다.

 

이터레이터는 { value, done } 객체를 반환하는 next() 메소드를 가진 객체라고 했었는데 next() 메소드를 확인해보자.

 

console.log(arrIter.next()); // {value: 1, done: false}
console.log(arrIter.next()); // {value: 2, done: false}
console.log(arrIter.next()); // {value: 3, done: false}
console.log(arrIter.next()); // {value: undefined, done: true}

 

보는 것과 같이 { value, done } 객체가 반환 된 것을 볼수 있는데, value값이 있을 때는 done이 false값을 가지고, 이제 순회할 것 이 없는 상황에는 done이 true값을 가진다.

 

또 다른 예제를 통해 알아보자.

 

const arrIter2 = arr[iterator]();

arrIter2.next();

for(const v of arrIter2){
   console.log(v);
}
// 2 3

 

반복문 전에 next()를 한번 해서 첫번째 값 1은 출력이 안된 것을 확인 할 수 있다.

 

마지막으로 map에서는 다음과 같이 순회가 가능하다.

 

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map.keys()) console.log(a); // a b c
for (const a of map.values()) console.log(a); // 1 2 3
for (const a of map.entries()) console.log(a); // ['a', 1] ['b', 2] ['c', 3]

※map 함수

.keys(): key값 출력

.values(): value값 출력

.entries(): key, value값 출력

 


#3 사용자 정의 이터러블을 통해 알아보기

 

사용자 정의 이터러블

 

이터러블 프로토콜을 따라서 순회 동작을 사용자 정의할 수 있다는 점이 이터러블을 활용하는데 있어 중요한 의의를 가진다.

 

const iterable = {
   [Symbol.iterator]() {
      let i = 3;
      return {
         next() {
            return i === 0 ? { value: undefined, done: true } : { value: i--, done: false };
         }, // ① next()함수 구현
         [Symbol.iterator]() { return this; } 
         // ② Symbol.iterator을 실행했을때 자기자신을 리턴하도록 하기
      }
   }
};

let iter2 = iterable[Symbol.iterator](); // ②가 없으면 실행 불가
console.log(iter2[Symbol.iterator]() == iter2); // true
iter2.next();
for (const a of iter2) console.log(a); // 2 3