본문 바로가기

Javascript

Array, Set, Map 을 통해 알아보는 이터러블 / 이터레이터 - JS ES6+ 함수형 프로그래밍 - 5

본 시리즈는 인프런 강의 함수형 프로그래밍과 JavaScript ES6+ ( 지식 공유자 : 유인동 님 ) 의 강의를 수강하면서 내용을 제 방식대로 포스팅하는 글입니다.

 

함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의

ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,

www.inflearn.com

환경 : node.js 기반, 동작하지 않는것은 브라우저 환경

array, set, map 모두 for of 문으로 순회할수 있다.

각각의 차이점과 공통점을 살펴보면서

es6 에서 for of 문이 어떻게 동작하고 어떻게 리스트를 es6 에서 순회하는지 어떻게 추상화 되어있는지 정확하게 알아보자

실제로 for of 문은 for (let i ...) 와 동일하게 동작하지 않는다.

Array를 통해 알아보기

  log("Arr-------");
  const arr = [1, 2, 3];

  for (const a of arr) log(a);
  
  // log(arr[0]) // 1
  // log(arr[1]) // 2
  
  // Array 는 key 로 접근해서 값을 알수가 있음

Set을 통해 알아보기

  log("Set-------");
  const set = new Set([1, 2, 3]);
  for (const a of set) log(a);
  
  // log(set[0]); // undefined
  // log(set[1]); // undefined
  // log(set[2]); // undefined
  
  // key 로 값이 조회되지 않는다.
  // 다시말하자면, for of 문은 for (let i ...) 과 같이 생기지 않았다는것을 의미한다.

Map을 통해 알아보기

  log("Map-------");
  const map = new Map([
    ["a", 1],
    ["b", 2],
    ["c", 3],
  ]);
  for (const a of map) log(a);
  
  // log(set[0]); // undefined
  // log(set[1]); // undefined
  // log(set[2]); // undefined
  
  // Map 또한 key 로 값이 조회되지 않는다.

es6 에서는 Symbol 이라는것이 추가되었다.

Symbol.iterator;
// Symbol 은 어떤 객체의 key 로 사용될수 있다.

Array 도 Symbol.iterator 로 접근할수 있다.

  log("Arr-------");
  let arr = [1, 2, 3];
  log(arr[Symbol.iterator]); // [Function: values]
  arr[Symbol.iterator] = null; // arr key 로 접근할수 있던 함수를 비워버린것

  for (const a of arr) log(a); // Uncaught TypeError: arr is not iterable

  // for of 문과 Symbol.iterator 가 연관이 있다.

set 과 map 도 마찬가지로 Symbol.iterator 로 접근할수 있다.

이터러블의 정의

Array, Set, Map 은 JS 의 내장 객체로써 이터러블 / 이터레이터 프로토콜을 따른다

이터러블 ( iterable ) / 이터레이터 ( iterator ) 프로토콜
- 이터러블 : 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진값
- 이터레이터 : { value, done } 객체를 리턴하는 next() 를 가진 값
- 이터러블/이터레이터 프로토콜 : 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록 한 규약

Array 는 이터러블이다.

그 이유는 Array 가 [Symbol.iterator]() 를 가졌기 때문이다.

  log("Arr-------");
  let arr = [1, 2, 3];
  log(arr[Symbol.iterator]()); // Object [Array Iterator] {}

  for (const a of arr) log(a);

이터레이터는 next() 라는 메서드를 가졌고, 객체를 리턴한다.

  log("Arr-------");
  let arr = [1, 2, 3];
  let iter = arr[Symbol.iterator]();

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

  for (const a of arr) log(a);

이를 다시 해석하면

for ... of 문은 iterator 를 next() 하며 순회하고, value 에 있는 값을 반환시키다가, done 이 true 가 되면 구문을 빠져 나오도록 되어있는 것이다 !

  log("Arr-------");
  
  let arr = [1, 2, 3];
  let iter1 = arr[Symbol.iterator]();

  iter1.next();

  for (const a of iter1) log(a); // 2 3

Set 은 어떨까?

  log("Set-------");
  const set = new Set([1, 2, 3]);

  // log(set[0]); // undefined
  // log(set[1]); // undefined
  // log(set[2]); // undefined

  // Set 객체는 이터러블 프로토콜을 따르고 있고, for...of 문도 이터러블 프로토콜을
  // 따르고 있기 때문에 동작하는 것이다.

  let iter1 = set[Symbol.iterator]();

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

  for (const a of set) log(a);

이제, Map 을 살펴보자

  log("Map-------");
  const map = new Map([
    ["a", 1],
    ["b", 2],
    ["c", 3],
  ]);

  let iter1 = map[Symbol.iterator]();
  let iter2 = map[Symbol.iterator]();

  log(iter1.next()); // { value: [ 'a', 1 ], done: false }
  log(iter1.next()); // { value: [ 'b', 2 ], done: false }
  log(iter1.next()); // { value: [ 'c', 3 ], done: false }
  log(iter1.next()); // { value: undefined, done: true }
  
  iter2.next();
  
  // Map 객체는 value 로 array 를 리턴한다.
  
  for (const a of map) log(a); // [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
  for (const b of iter2) log(b); // [ 'b', 2 ] [ 'c', 3 ]

Map 은 자체적으로 메서드를 내장하고 있다.

  log(map.keys()); // [Map Iterator] { 'a', 'b', 'c' }

  let iter3 = map.keys();

  log(iter3.next()); // { value: 'a', done: false }
  log(iter3.next()); // { value: 'b', done: false }
  log(iter3.next()); // { value: 'c', done: false }
  log(iter3.next()); // { value: undefined, done: true }
  
  for (const c of map.keys()) log(c); // a b c
  for (const c of map.values()) log(c); // 1 2 3
  for (const c of map.entries()) log(c); // [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]

추가적으로, Map 객체는 메서드를 내장하고 있는데,

  // Map 의 내장 메서드를 사용한 값은 이터레이터이다.

  let iter4 = map.values();
  log(iter4[Symbol.iterator]); // [Function: [Symbol.iterator]]

  // Symbol.iterator 를 그대로 가지고 있기 떄문에 for of 문에서
  // entries() 의 결과를 심볼 이터레이터를 실행한것을 가지고 다시 순회를 하는것이다.

  let iter5 = iter4[Symbol.iterator]();

  // values() 를 사용해 리턴된 이터레이터를 다시 이터레이터 추출하게되면
  // 자기 자신이 다시 나오도록 되어있다.

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

이렇게 동작하는것이 이터러블 / 이터레이터 프로토콜 이다.