//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 이라는 내장값 들을 가지고 있고 이를 for of문 으로 순회 가능하다.
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 문이 동작한다. ( 인덱스로 인덱싱이 가능 )
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와 다르게 동작 한다는 것을 알 수 있다.
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
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은 자바스크립트 내장객체 로서 이터러블, 이터레이터 프로토콜을 따르고 있다.
[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 {}
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 }
이를 이용하여 위에서 했던 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, 전개연산자 등 이터러블, 이터레이터 프로토콜을 따르는 동작(?)을 할 수 있는 것이다.