자바스크립트에는 Array와 Set과 Map이라는 내장함수를 가지고 있는데 이 값들 모두 for of 문으로 순회할 수 있다.
각각의 차이점들이나 공통점들을 살펴보자. 이걸 통해서 es6에서 for of문이 어떻게 동작하고 어떻게 list를 es6에서 순회하는지 어떻게 추상화 되어있는지 정확하게 알아보자.
아래와 같이 순회하는 코드를 작성해본다.
for of문이 arr를 순회를 할 때 앞서 es5에서 사용했었던 방법으로 돌아갈까?(list에 length만큼 loop를 돌면서 i를 증가하면서 0부터 한번씩 i라는 키로 접근해서 한번씩 value를 순회하는 식으로 for문이 돌아가고 숨겨져 있는 것일까?)
그렇지는 않다.
### Array를 통해 알아보기
<script>
log('Arr -----------------------------');
const arr = [1, 2, 3];
for (const a of arr) log(a);
</script>
### Set을 통해 알아보기
<script>
log('Set -----------------------------');
const set = new Set([1, 2, 3]);
for (const a of set) log(a);
</script>
### Map을 통해 알아보기
<script>
log('Map -----------------------------');
const map = new Map([['a', 1], ['b,', 2], ['c', 3]]);
for (const a of map) log(a);
</script>
살펴보면 array같은 경우에는 key로 접근을 해서 안에 있는 값을 순회를 할 수 있다.
arr[0]
1
arr[1]
2
arr[3]
undefined
arr[2]
3
set이라는 값은 똑같이 조회를 할 수가 없다. 이렇게 값이 조회가 되지 않는다는 것은 for of문이 내부적으로 es5처럼 생기지 않았다는 것이다.
map같은 경우에도 같은 접근을 할 수가 없다. 즉 set와 map은 key와 그에 해당하는 값들이 연결되면서 동작하는 것이 아니라는 것이다.다른 방법으로 for of문이 동작한다는 것인데 어떤 규약일까?
set
Set(3) {1, 2, 3}
set[0]
undefined
set[1]
undefined
set[3]
undefined
//-----------
map
Map(3) {"a" => 1, "b," => 2, "c" => 3}
map[0]
undefined
map[1]
undefined
map[2]
undefined
아래와 같은 iterator이 있다.(Symbol iterator) es6에서 추가된 Symbol이다. 즉 for of문과 Symbol iterator에 담겨져 있는 함수가 연관이 있을 수 있다고 생각할 수 있다.
log(arr[Symbol.iterator]);
=>
ƒ values() { [native code] }
arr[Symbol.iterator] = null;
=>
index.html:32 Uncaught TypeError: arr is not iterable
at index.html:32
Set도 마찬가지인데 접근할 수 있는 어떤 값이 있다. map도 마찬가지이다. 각각들이 Symbol iterator에 구현되어 있는 함수, 메소드를 Arr와 Set, Map이 다 가지고 있다.
set[Symbol.iterator]
=>
ƒ values() { [native code] }
map[Symbol.iterator]
=>
ƒ entries() { [native code] }
여기서 이터러블 이터레이트 프로토콜에 대해서 보면
일단 Arr와 Set과 Map은 자바스크립트에 있는 내장객체로써 이터러블 이터레이터 프로토콜을 따르고 있는데
이터러블, 이터레이터, 이터러블/이터레이터 프로토콜 각각의 정의는 아래와 같다.
- 이터러블: 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값
- 이터레이터: { value, done } 객체를 리턴하는 next()를 가진 값
- 이터러블 / 이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약
arr는 이터러블인데 이유는 arr가 이터레이트를 리턴하는 [Symbol.iterator]() 를 가지고 있기 때문이다.
그리고 이걸 실행했을 때 Iterator을 리턴한다고 되어있다.
arr
=>
(3) [1, 2, 3]
arr[Symbol.iterator]
=>
ƒ values() { [native code] }
arr[Symbol.iterator]()
=>
Array Iterator {}
이터레이터는 객체를 리턴하는 next()를 가진 값으로 되어져 있는데 iterator는 next 매소드를 가지고 있고 실행했들 때 아래와 같은 코드를 볼 수 있다. 잭 객체를 리턴하는 값을 이터레이터라고 한다.
let iterator = arr[Symbol.iterator]()
undefined
iterator.next()
{value: 1, done: false}
iterator.next()
{value: 2, done: false}
iterator.next()
{value: 3, done: false}
iterator.next()
{value: undefined, done: true}
for of문 같은 경우에는 Arr가 이터러블이고 Arr는 Symbol.iterator를 통해서 이터레이터를 리턴하기 때문에for of문과 함께 잘 동작하는 이터러블 객체이고 그렇게 해서 for of를 순회할 수 있기 때문에 이터러블/이터레이터 프로토콜을 따른다라고 할 수 있다.
다시 정리하면 arr는 Symbol.iterator라는 메소드를 가지고 있고 함수를 실행했을 때는 Array Iterator{}가 떨어진다.
arr[Symbol.iterator]
ƒ values() { [native code] }
arr[Symbol.iterator]()
Array Iterator {}
iterator가 반환되고 iterator가 실행되었을 때 안에 있는 값들이 계속해서 떨어지다가 어느 시점부터는 done이 true이고 value가 아무거도 없게 떨어지고 있다.
이렇게 next가 뽑아주는 것과 동일하게 for of문에서는 value에 들어오는 값을 a에 담아서 출력하고를 반복하다가
done이 true가 되면 for of문을 빠져나오게 되어있다.
let iterator = arr[Symbol.iterator]()
undefined
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}
iter1이라는 곳에 값을 넣고 next를 3번하게 되면 값을 출력하지 않고 넘어가게 된다. 즉 arr는 Symbol iterator를 실행한 iterator를 계속해서 순회하면서 안쪽에 value로 떨어지는 값을 출력하고 있는 것이다.
<script>
log('Arr -----------------------------');
const arr = [1, 2, 3];
let iter1 = arr[Symbol.iterator]();
iter1.next();
iter1.next();
iter1.next();
for (const a of iter1) log(a);
</script>
=>
Arr -----------------------------
<script>
log('Arr -----------------------------');
const arr = [1, 2, 3];
let iter1 = arr[Symbol.iterator]();
for (const a of arr) log(a);
</script>
=>
Arr -----------------------------
1
2
3
set역시 마찬가지이다. 아래와 같이 결과값이 뽑히기 때문에 똑같이 순회가 되는 것이다.
var a = set[Symbol.iterator]();
undefined
a.next();
{value: 1, done: false}
a.next();
{value: 2, done: false}
a.next();
{value: 3, done: false}
a.next();
{value: undefined, done: true}
<script>
log('Set -----------------------------');
const set = new Set([1, 2, 3]);
for (const a of set) log(a);
</script>
=>
Set -----------------------------
1
2
3
map같은 경우에는 value에 각각의 값이 담겨있기 때문에 map 역시 아래와 같이 코드결과가 출력된다.
var a = map[Symbol.iterator]();
undefined
a.next();
{value: Array(2), done: false}
done: false
value: (2) ["a", 1]
[[Prototype]]: Object
a.next();
{value: Array(2), done: false}
done: false
value: (2) ["b,", 2]
[[Prototype]]: Object
a.next();
{value: Array(2), done: false}
done: false
value: (2) ["c", 3]
[[Prototype]]: Object
a.next();
{value: undefined, done: true}
done: true
value: undefined
[[Prototype]]: Object
map역시도 아래코드를 작성해서 출력을 하고 next를 한번 진행한 상태에서 for of문을 담게 되면 두번의 값만 순회하게 된다. 이런식으로 동작하게 되어있다.
<script>
log('Map -----------------------------');
const map = new Map([['a', 1], ['b,', 2], ['c', 3]]);
var iter2 = map[Symbol.iterator]();
iter2.next();
for (const a of iter2) log(a);
</script>
=>
Map -----------------------------
["b,", 2]
["c", 3]
그리고 map같은 경우에는 keys라는 함수가 있는데 keys라는 함수는 이터레이터를 리턴한다. 이터레이터는 next라 했을 때 value에 key만 담게 된다.
map
Map(3) {"a" => 1, "b," => 2, "c" => 3}
map.keys();
MapIterator {"a", "b,", "c"}
var a = map.keys();
undefined
a.next();
{value: "a", done: false}
a.next();
{value: "b,", done: false}
a.next();
{value: "c", done: false}
a.next();
{value: undefined, done: true}
그렇다는 이야기는 아래코드를 실행했을 때(map.keys()) 키만 뽑을 수도 있다는 것이다.values라는 함수도 있고 entries라는 함수도 있다.keys는 키만 values는 값만 entries는 전체 다 뽑아준다. 이렇게 해서 순회를 한다.
<script>
log('Map -----------------------------');
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map.keys()) log(a);
for (const a of map.values()) log(a);
for (const a of map.entries()) log(a);
// for (const a of iter2) log(a);
</script>
Map -----------------------------
a
b
c
1
2
3
["a", 1]
["b", 2]
["c", 3]
즉 map 코드는 아래와 같은 형식으로 iterator를 돌 수 있게 된다.
map.values()
MapIterator {1, 2, 3}
var it = map.values();
undefined
it[Symbol.iterator]
ƒ [Symbol.iterator]() { [native code] }
var it2 = it[Symbol.iterator];
undefined
var it2 = it[Symbol.iterator]();
undefined
it2.next();
{value: 1, done: false}
it2.next();
{value: 2, done: false}
it2.next();
{value: 3, done: false}
<출처 : 유인동 함수형 프로그래밍과 JavaScript ES6+>
https://www.inflearn.com/course/functional-es6/dashboard
함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의
ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,
www.inflearn.com
'JavaScript > 함수형 프로그래밍과 JavaScript ES6+' 카테고리의 다른 글
odds(제너레이터와 이터레이터) & for of etc.. (0) | 2021.08.17 |
---|---|
제너레이터와 이터레이터 (0) | 2021.08.17 |
사용자 정의 이터러블, 이터러블/이터레이터 프로토콜 정의 & 전개 연산자 (0) | 2021.08.17 |
es6에서 순회, iterable: iterator protocol(기존과 달라진 es6에서의 list 순회) (0) | 2021.08.17 |
함수형 자바스크립트 기본기(평가와 일급, 일급 함수, 고차 함수) (0) | 2021.08.17 |