함수형 프로그래밍 2 Iterator 프로토콜

리스트를 순회하는 방법엔 여러방법과 Iterator에 대해 알아보려고 한다.

for i++

ES6 이전에는 for문을 통하여 리스트를 순회하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 배열 순회
const list = [1, 2, 3];
for (var i = 0; i < list.length; i++) {
console.log(list[i]);
}
// 유사 배열 순회
const str = "abc";
for (var i = 0; i < str.length; i++) {
console.log(str[i]);
}

// 1
// 2
// 3
// a
// b
// c

for of

ES6에선 for of문이 추가되어 좀 더 명령형에서 선언형으로 바뀌었다.
인덱스로 직접 접근할 수 없는 리스트도 순회할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//배열 순회
const list = [1, 2, 3];
for (const a of list) {
console.log(a);
}
//유사배열 순회
const str = "abc";
for (const a of str) {
console.log(a);
}

// 1
// 2
// 3
// a
// b
// c

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

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

Iterable/Iterator

for of는 Iterable을 이용하여 구현 되어있어 동작원리를 이해하기 위해선 Iterable에 대해 먼저 이해해야한다.

Iterable

실행시에 Iterator를 리턴하는 함수인 Symbol.iterator 를 가진 값이다.

Iterable이 되기 위해선 다음과 같은 조건을 만족해야한다.

  • 객체 내에 [Symbol.iterator] 메서드가 존재해야 한다.
  • [Symbol.iterator] 메서드는 Iterator 객체를 반환해야 한다.

Iterator

{value,done} 객체를 리턴하는 next()를 가진 값이다.

  • 객체 내에 next 메서드가 존재해야 한다.
  • next 메서드는 IteratorResult 객체를 반환해야 한다.
  • IteratorResult 객체는 done: boolean과 value: any 프로퍼티를 - 가진다.
  • 이전 next 메서드 호출의 결과로 done 값이 true를 리턴했다면, 이후 호출에 대한 done 값도 true여야 한다.

Well-formed iterable

Iterator면서 Iterable인 객체를 Well-formed iterable이라고 한다.
즉 이터레이터의 이터러블이 자기 자신을 반환하면 Well-formed iterable이다.

Iterable/Iterator 프로토콜

Iterable을 for…of, 전개 연산자 등과 함께 동작하도록 한 규약이다.

for of의 동작원리

  1. for of는 Iterable 객체에 사용할 수 있다.
  2. for of를 호출하게 되면 Symbol.iterator를 통해 Iterator를 반환한다.
  3. 즉 for of는 Iterable/Iterator 프로토콜에 따라 동작한다.
1
2
3
4
5
6
7
const list = [1, 2, 3];
for (const a of list) {
// for of는 Iterable/Iterator 프로토콜에 따라 동작한다.
// 즉 반환된 Iterator의 value를 a로 참조한다.
// done이 true가 되면 반복문을 빠져나온다.
console.log(a);
}

따라서 코드를 다음과 같이 수정할 수 있다.

1
2
3
4
5
6
7
let list = [1, 2, 3];
let iterator = list[Symbol.iterator]();
while (1) {
const { value, done } = iterator.next();
if (done === true) break;
console.log(value);
}

for of 응용하기

Map과 Set에는 Iterator를 반환하는 여러 메서드가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for (const a of map.keys()) console.log(a);
// Map,Set의 key를 value로 하는 Iterator를 반환하는 메서드이다.
// a
// b
// c
for (const a of map.values()) console.log(a);
// Map,Set의 value를 value로 하는 Iterator를 반환하는 메서드이다.
// 1
// 2
// 3
for (const a of map.entries()) console.log(a);
// Map,Set 전체를 value로 하는 Iterator를 반환하는 메서드이다.
// ['a',1]
// ['1',2]
// ['1',3]

사용자 정의 Iterable

위에서 언급한 조건을 준수하는 Iterable 객체를 정의하여 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const iterable = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i == 0 ? { done: true } : { value: i--, done: false };
},
};
},
};

let iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {done:true}

for (const a of iterable) console.log(a);

// Uncaught TypeError: iterator is not iterable
// Well-formed Iterable이여야 에러가 발생하지 않는다.

Well-formed Iterable을 정의하려면 자기 자신을 반환하는 iterator를 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const iterable = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i == 0 ? { done: true } : { value: i--, done: false };
},
[Symbol.iterator]() {
return this;
},
};
},
};

for (const a of iterable) console.log(a);

// Well-formed iterator여야 에러가 발생하지 않는다.

전개 연산자

전개연산자도 Iterable 프로토콜을 따르고 있다.

1
2
3
4
5

const a=[1,2=];
console.log([...a,...[3,4]]) ///[1,2,3,4];
a[Symbol.iterator]=null;
console.log([...a,...[3,4]]) // Uncaught TypeError: a is not iterable

Iterable 프로토콜을 따르고 있기 때문에 Map,Set과 같은 여러 Iterable객체에 적용된다.

1
2
3
4
5
6
7
8
9
const set = new Set([1, 2, 3]);
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);

console.log([...set, ...map]);
// [1,2,3,["a", 1],["b", 2],["c", 3]]

Ref

[Javascript] Iterable과 Iterator 이해하기

함수형 프로그래밍과 JavaScript ES6+

댓글