[함수형 프로그래밍과 ES6+] 02. ES6에서의 순회와 이터러블:이터레이터 프로토콜

Kyle·2021년 1월 5일
1

리스트 순회

본 글은 유인동 님의 함수형 프로그래밍과 ES6+의 2강을 보고 작성한 글입니다.

ES6 이전의 순회 방법

for 문으로 배열의 숫자로된 key값을 접근해서 배열을 순회했다.

const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
//1
//2
//3

ES6 이후의 순회 방법

for...of 구문이 생겼다.
Array, Set, Mapfor...of 문을 사용하면서 이터러블이 어떤것인지 알아보자.

//Array,Set,Map 으로 알아보기
//Array
const arr = [1, 2, 3];
for (const x of arr) console.log(x);

//Set
const set = new Set([1, 2, 3]);
for (const x of set) console.log(x);

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

for...of를 이용해 Array, Set, Map 을 순회할 수 있다.

그럼 for...of 문은 ES6전의 for문을 그대로 사용하는 것일까??

  • arr[0] = 1
  • arr[1] = 2
  • arr[2] = 3

이 된다. 그럼 Set, Map은?

  • set[0] = undefined
  • map[0] = undefined

그렇다. Array와는 다르게 Set, Map은 숫자로된 Key를 이용하지 않는다. 즉, ES6이전의 for문과는 다르게 for...of문이 이루어 진다는 것이다.

MDN_for...of에서는 for...of 문을 다음과 같이 정의하고 있다.

The for...of statement creates a loop iterating over iterable objects

for...of 문은 이터러블 객체를 반복할 수있는 루프를 만든다.

이제 이터러블 객체란 무엇인지에 대해 알아보자.

이터레이션 프로토콜

이터레이션 프로토콜은 데이터 컬렉션을 순회하기 위한 프로토콜입니다. 이터레이션 프로토콜에는 2가지 프로토콜이 있습니다.

  • iterable protocol
  • iteration protocol

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

Array, Set, Map은 자바스크립트 내장 객체로 이터러블, 이터레이터 프로토콜을 따르고있다. 각 용어의 정의를 먼저 알아보자.

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

Map을 예제로 확인해 보겠습니다.

  • Map은 이터러블 객체입니다.

  • 이터러블 객체는 이터레이터를 리턴하는 [Symbol.iterator]메소드를 갖고 있습니다.

//prettier-ignore
const map = new Map([['a', 1],['b', 2],['c', 3]]);
console.log(map[Symbol.iterator]); // ƒ values() { [native code] }
let iterator = map[Symbol.iterator]();
console.log(iterator); // MapIterator {"a" => 1, "b" => 2, "c" => 3}
  • 이터레이터{value,done} 객체를 리턴하는 next() 를 가진 값입니다. 위의 MapIterator에 next 메소드를 호출해 보겠습니다.
iterator.next(); //{value: 1, done: false}
iterator.next(); //{value: 2, done: false}
iterator.next(); //{value: 3, done: false}
iterator.next(); //{value: undefined, done: true}
iterator.next(); //{value: undefined, done: true}
console.log(iterator); // MapIterator {}

이터레이터의 next메소드를 호출할 때마다 value값을 순회하면서 모든 value값을 다 순회했을 시 done에 true값이 들어오게 됩니다.

for..of 구문에서는 이러한 이터레이터를 활용해서 next메소드를 호출할 때마다 반환되는 객체의 value를 done:true가 되기 전까지 순회하는 것입니다.

let iterator = map[Symbol.iterator]();
for (let x of iterator) console.log(x);
//["a", 1]
//["b", 2]
//["c", 3]
console.log(iterator); // MapIterator {}
iterator.next(); //{value: undefined, done: true}

for..of 구문에서 iterator을 next()를 호출하며 다 순회하였기 때문에 for문이 끝난 iterator의 value는 비어있게 되는 것으로 확인해볼 수 있습니다.

keys,values,entries 같은 함수

Map의 keys(),values(),entries() 같은 함수를 이용해 Map의 key값, value값, key-value쌍 값들을 얻을 수 있습니다.

map.keys(), map.values(), map.entries()같은 것들은 어떻게 for...of문으로 접근이 가능한 지 알아보겠습니다.

아래 사진과 같이 각각의 값들은 MapIterator의 형태로 만들어져 있습니다.

또한 이 MapIterator들은 [Symbol.iterator]()를 가졌으며 자기 자신을 반환합니다.
entries()로 예를 들어보겠습니다.

const mapEntries = map.entries();
const it = mapEntries[Symbol.iterator]();
console.log(it); //MapIterator {"a" => 1, "b" => 2, "c" => 3}
console.log(it[Symbol.iterator]() === it); // true
it.next(); //{value: Array(2), done: false} Array(2) = ['a',1]

위와 같이 이터레이터가 자기 자신을 반환하는 Symbol.iterator()를 갖는 형태를 well-formed iterator라고 할 수 있습니다.

위 코드의 결과값 같이 실행 되기 때문에 Map또는 Set의 keys(), values(), entries()등도 for..of문을 사용할 수 있습니다.
즉, 이터러블 객체라고 볼 수 있습니다.

여기까지 잘 들으셨는지 퀴즈를 맞춰봅시다 ㅎㅎ

아래 2개의 코드의 결과를 예상해보세요

//1번
const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3],
]);

const mapKey = map.keys();

for (let x of mapKey) console.log(x);
for (let x of mapKey) console.log(x);
//2번
const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3],
]);

const mapKey = map.keys();
const mapKeyIter = mapKey[Symbol.iterator]();

for (let x of mapKey) console.log(x);

for (let x of mapKeyIter ) console.log(x);

위와같이 이터러블 객체를 for...of 또는 전개 연산자 등과 함께 동작하도록 한 규약을 이터러블/ 이터레이터 프로토콜이라 합니다.

즉, array, Set, Map, Stiring 뿐만 아니라 keys(), values(), entries(), Nodelist, Arguments도 이터러블/ 이터레이터 프로토콜를 따른다 라고 말할 수 있습니다.

전개연산자

너무 유용한 ES6 문법인 spread Syntax도 이터러블 객체이기 때문에 활용 가능한 것입니다.

let a = [1, 2, 3];
a[Symbol.iterator] = null;
let k = [...a]; // TypeError : a is not iterable 에러를 발생 시킨다.

Array, Map, Set 모두 이터러블 객체이기 때문에 전개 연산자를 활용할 수 있다.

전개연산자를 보면서 잘 모르겠는 한가지 부분이 있습니다.

이해가 되지 않는 부분

spread syntax도 iterable 객체들이 사용가능 한 것이다. 아래의 예시를 보면 일반 객체인 obj는 String과 달리 안되는 것을 확인할 수 있다.

let obj = {1:1,2:2,3:3,4:4}
console.log(...obj) // Found non-callable @@iterator

const str = 'hello'
console.log(...str) //h e l l o

근데 아래와 같이 할 때는 왜 될까?

let copyObj = {...obj}
console.log(copyObj) //{1: 1, 2: 2, 3: 3, 4: 4}

출처

profile
Kyle 발전기

2개의 댓글

comment-user-thumbnail
2021년 3월 18일

이터러블이 뭔지 항상 궁금했었는데 간단한 개념이 아니었네요 어렵숩니다🤯

1개의 답글