/**
* ES5
*/
const list = [1,2,3]
const str = 'list'
for(var i = 0; i < list.length; i++){ // 배열
console.log(list[i])
}
for(var i = 0; i < str.length; i++){ // 유사 배열
console.log(str[i])
}
/**
* ES6
*/
const list = [1,2,3]
for (const el of list){
console.log(el)
}
ES6 규격이 되면서 순회 문법이 간결해지고 선언적으로 변했는데 단순히 문법의 변화 이상의 큰 의미가 있다.
ES6 에서 리스트 순회를 어떻게 추상화하고 개발자에게 사용하게 했는지 상세히 알아보자!
이터러블: 이터레이터를 리턴하는 [Symbol.iterator]() 메서드를 포함한 값 ( 배열, set, map 등)
반복가능한 값들이 Symbol.iterator 메서드를 가진게 아니라 Symbol.iterator 메서드가 있기에 반복이 가능해지는 것인듯
이터레이터: 이터러블의 Symbol.iterator 메서드를 실행했을 때 반환한 값으로 { value, done } 객체를 리턴하는 next() 를 가진 값
이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약
자바스크립트에서 순회 가능한 데이터 타입은 총 3 종류로 Array, Set, Map 이 있다.
Set : 이터러블을 인자로 받아 Set 인스턴스로 만들어준다. ( 프로토타입 Set이며 Symbol.iterator 메서드를 포함한 반복 가능한 객체이다. )
Map : key와 value로 이루어진 이터러블을 인자로 받아 Map 인스턴스로 만들어준다.
const arr = [1,2,3]
const set = new Set([1,2,3])
const map = new Map([['name','김태형'],['age',33],['height',175]])
console.log(arr[0]) // 1
console.log(set[0]) // undefined
console.log(map[0]) // undefined
for(const v of arr) console.log(v) // 1 2 3
for(const v of set) console.log(v) // 1 2 3
for(const v of map) console.log(v) // ['name','김태형] ['age',33] ['height',175]
Array는 두 방법 모두 허용되나 Set과 Map은 for...of 문 만 허용된다.
Set, Map 이 [index] 로 요소에 접근이 불가하다는 것은 for...of 문의 작동 방식이 기존 ES5 에서의 배열 순회 방법과 차이가 있다는것을 알 수 있다.
즉, 배열의 index가 아닌 다른 형태의 순회 방법을 사용하고 있다는 것인데 여기에서 바로 이터러블/이터레이터 프로토콜을 사용한 순회를 한다는 것을 알 수 있다.

Array 를 console로 찍어보면 내부에 Symbol.iterator 메서드를 포함한게 보인다.
Symbol 은 ES6 에서 새로 정의된 기본 타입 데이터로 객체와 배열의 키로 사용할 수 있다.
위에 살펴본 사진에서 배열은 Symbol.iterator 을 내부에 가지고 있으므로
const arr = [1,2,3]
console.log(arr[Symbol.iterator]) // values() { [native code] } 함수 반환
console.log(arr[Symbol.iterator]()) // Array Iterator {} 이터레이터 반환
이렇게 반환된 이터레이터는 next 메서드를 통해 이터레이터를 순회할 수 있다.
순회하다 마지막 요소에 다다르면 done 을 true로 변경하며 더이상 순회하지 않게 된다.
for..of 는 이터러블/이터레이터 프로토콜을 지키는 순회 방식을 사용합니다.
const arr = [1,2,3]
arr[Symbol.iterator] = null // 이터레이터 강제로 제거하기
for(const v of arr) console.log(v) // 에러발생 ! Uncaught TypeError: arr is not iterable
결론
for...of 문법은 이터러블/이터레이터 프로토콜을 준수하는 순회 방식을 사용한다.
Array도 이터러블/이터레이터 프로토콜을 준수한다. 다만, Set, Map 은 이터러블/이터레이터 프로토콜만을 준수하는 데이터 타입이기 때문에 Set, Map 은 [0] 과 같은 인덱스형 접근이 불가능한것.
이터러블은 Symbol.iterator 메서드를 포함한 값이라고 했는데요. 그리고 Symbol.iterator 메서드를 실행하면 이터레이터를 반환한다고 했습니다. 그런데 이렇게 반환된 이터레이터 객체 또 한 Symbol.iterator 메서드를 포함하고있습니다.
그렇다면 이터레이터는 이터러블 이겠죠? 그럼 Array, set, map 각 요소의 {value, done} 을 반환하는 것과 어떤점이 다를까요?
const arr = [1,2,3]
const iter = arr[Symbol.iterator]()
console.log(arr[Symbol.iterator]) //[Symbol.iterator]() { [native code] }
console.log(arr[Symbol.iterator]()) //Array Iterator {}
console.log(iter.next()) // {value:1, done:false}
const iter2 = iter[Symbol.iterator]()
console.log(iter2.next()) // {value:2, done:false}
console.log(iter === iter2) //true
// 자바스크립트 Array Iterator {} 객체의 Symbol.iterator 는 자기 자신을 반환하도록 설계되어있다!
[Symbol.iterator]() {
return this;
}
즉, 이터레이터의 Symbol.iterator 메서드는 자기 자신을 반환한다.
당연한 이야기지만 이터레이터도 객체기 때문에 다른 변수에 할당하면 원본을 공유한다.
const arr = [1,2,3]
const iter = arr[Symbol.iterator]()
for(const v of iter)console.log(v) // 1, 2, 3
const iter2 = iter[Symbol.iterator]()
console.log(iter2.next()) // {value:undefiend, done:true}