함수형 프로그래밍(이터러블, 이터레이터)

기존과 달라진 ES6 리스트 순회

//ES5
const list = [1,2,3]
for(var i=0; i<list.length; i++){
  console.log(list[i])
} // length에 의존

const str = '123'
for(var i=0; i<str.length; i++){
  console.log(str[i])
}

//ES6
for(const a of list) console.log(a)
for(const a of str) console.log(a) //좀 더 간결해짐.

Array, Set, Map

자바스크립트에는 Array, Set, Map 이라는 내장값 들을 가지고 있고 이를 for of문 으로 순회 가능하다.

  • Array

const log = console.log
//Array
const arr = [1, 2, 3]
for(const a of arr) log(a) // 1  2  3

log(arr[0]) // 1 ( 인덱스로 인덱싱 가능.)

Array 문은 내부적으로 ES5의 for문이 동작하는 것과 같은 방식으로 for of 문이 동작한다. ( 인덱스로 인덱싱이 가능 )

  • Set

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

log(set) // Set(3) {1,2,3}
log(set[0]) //undefined

for of 문을 동작 했을 때 출력되는 값은 Array와 같지만 인덱스로 인덱싱을 하려고 했을 때 값이 없다. 이 말은 for of문 내부적으로 Array와 다르게 동작 한다는 것을 알 수 있다.

  • Map

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

log(map) // Map(3) {"a" => 1, "b" => 2, "c" => 3}
log(map[0]) // undefined
  • Symbol

ES6에서 추가된 심볼을 이용하면 Symbol.iterator 를 통해 어떤 객체의 key로 사용 될 수 있다.

const arr = [ 1, 2, 3 ]
console.log(arr[Symbol.iterator]) // arr의 인덱싱을 가능하게 해주는 어떤 함수 출력.
arr[Symbol.iterator] = null // 객체의 key로 사용되는 Symbol iterator을 null로 만들어버림.
console.log(arr[0]) // Type error; arr is not iterable
  • 이터러블, 이터레이터 프로토콜

array, set, map은 자바스크립트 내장객체 로서 이터러블, 이터레이터 프로토콜을 따르고 있다.

  1. 이터러블: 이터레이터를 리턴하는 [Symbol.iterator] () 를 가진 값
// Array는 이터러블 이다.
const arr [1, 2, 3]
console.log(arr[Symbol.iterator]) // [Symbol.iterator]()
let iteraotr = arr[Symbol.iterator]()
console.log(iterator) // Array iterator {}
  1. 이터레이터 : value와 done이라는 key를 가지고 있는 객체( { value, done } ) 를 리턴하는 next() 를 가진 값
const arr = [1, 2, 3]
let iteraotr = arr[Symbol.iterator]()
console.log(iterator.next()) // { value : 1, done : false }
console.log(iterator.next()) // { value : 2, done : false }
console.log(iterator.next()) // { value : 3, done : false }
console.log(iterator.next()) // { value : undefined, done : true }
  1. 이터러블, 에터레이터 프로토콜 : 이터러블을 for…of, 전개 연산자 등과 함께 잘 동작하도록한 규약

이를 이용하여 위에서 했던 Array의 for…of 문 동작원리를 살펴보면

const log = console.log
const arr = [1,2,3]
let iterator = arr[Symbol.iterator]()
for(const a of arr) log(a)

iterator.next() 의에 나오는 value 값을 a에 계속해서 출력하는 것 이다.

참고.

iterator.next()
for(const a of iterator) log(a) // 2 3

next() 를 한번 호출 하였기 때문에 2부터 출력한다.

Set도 같은 원리를 따른다.

const log = console.log
const set = new Set([1,2,3])
log(set[Symbol.iterator]()) // SetIterator {1,2,3}

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

for(const a of iterator) log(a) // 출력 없음
for(const a of set) log(a) // 1 2 3

Set 역시 Array와 비슷하게 이터레이터의 next()를 호출 하였을 때 나오는 value값을 출력시키는 것 이다.

Map 역시 비슷하다.

const log = console.log
const map = new Map([['a','1'],['b','2'],['c','3']])
let iterator = map[Symbol.iterator]()
log(iterator) // MapIterator {"a" => "1", "b" => "2", "c" => "3"}
iterator.next()
for( const a of map ) log(a) // ['a','1'] ['b','2'] ['c','3']
for( const a of iterator) log(iterator) // ['b','2'] ['c','3']

이터레이터는 다르지만 Map역시 동작원리는 Array, Set과 같이 이터러블, 이터레이터 프로토콜을 따른다.

Map 객체에는 객체의 key에 접근 할 수 있는 keys(), value에 접근 할 수 있는 values()

key와 value 모두를 entry 형식으로 접근 할 수 있게 해주는 entries() 를 가지고 있다.

const keys = map.keys()
const values = map.values()
const entries = map.entries()
log(keys) // MapIterator {'a','b','c'}
log(values) // MapIterator {'1', '2', '3'}
log(entries) // MapIterator { 'a' => '1', 'b'=>'2', 'c'=>'3' }

이 함수들은 이터레이터를 리턴한다. ( 이터레이터를 리턴하니 이터러블 이고 이터러블은 이터러블, 이러레이터 프로토콜을 따르니 전개연산자, for … of 등 사용 가능! ) 이터레이터를 리턴하기 때문에

for(const a of keys) log(a) // 'a' 'b' 'c'
for(const b of values) log(b) // '1' '2' '3'
for(const c of entries) log(c) // ['a','1'] ['b','2'] ['c','3']

다음과 같이 for.. of 문으로 key값들을 출력 할 수 있다.

마지막으로..

const arr  = [1, 2, 3]
let it1 = arr[Symbol.iterator]() // Array iterator {}
log(it1[Symbol.iterator]) // iterator function
let it2 = it1[Symbol.iterator]() // Array iterator {}

이터레이터는 또 Symbol.iterator를 가지고 있다. 그리고 이 Symbol.iterator를 이용하여 이터레이터를 다시 만들면 자기자신이 반환 된다. 이 원리를 이용하여 for..of, 전개연산자 등 이터러블, 이터레이터 프로토콜을 따르는 동작(?)을 할 수 있는 것이다.


Written by@[HongDongUk]
공부한 것을 소소하게 적는 블로그.

GitHubFacebook