모던 JavaScript 튜토리얼을 따라가면서 정리합니다.
for..of
을 사용할 수 있는 객체를 이터러블이라고 부른다. 배열뿐만 아니라 문자열 등 다수의 내장 객체가 이터러블이다.
이터러블엔 메서드
Symbol.iterator
가 반드시 구현되어 있어야 한다.obj[Symbol.iterator]
의 결과는 이터레이터라고 부른다. 이터레이터는 이어지는 반복 과정을 처리한다.- 이터레이터엔 객체
{done: Boolean, value: any}
을 반환하는 메서드next()
가 반드시 구현되어 있어야 한다. 여기서done:true
는 반복이 끝났음을 의미하고 그렇지 않은 경우엔value
가 다음 값이 된다.
이터러블 객체 만들어보기
1 2 3 4
let range = { from: 1, to: 5 };
range 를 이터레이터로 만들려면 객체에
Symbol.iterator
라는 메서드를 추가한다. 그럼 다음과 같은 일이 벌어진다.for..of
가 시작되지 마자for..of
는Symbol.iterator
를 호출한다. 없다면 에러가 발생한다.Symbol.iterator
는 반드시iterator
(메서드next
가 있는 객체) 객체를 반환해야 한다.- 이후
for..of
는 반환된 객체만을 대상으로 동작한다. for..of
에 다음 값이 필요하면,for..of
는 이터레이터의next()
메서드를 호출한다.next()
의 반환값은{done: Boolean, value: any}
와 같은 형태여야 한다.done=true
는 반복이 종료되었음을 나타낸다.false
일 땐value
에 다음 값이 저장된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
let range = { from: 1, to: 5 }; range[Symbol.iterator] = function() { return { current: this.from, last: this.to, next() { if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; for (let num of range) { alert(num); // 1, 2, 3, 4, 5 }
이터러블 객체의 핵심은 관심사의 분리(Seperation of concern, SoC)에 있다.
range
에는 메서드next()
가 없다.- 대신
range[Symbol.iterator]()
를 호출해서 만든 ‘이터레이터’ 객체와 이 객체의 메서드next()
에서 반복에 사용될 값을 만들어낸다.
이렇게 하면 이터레이터 객체와 반복 대상인 객체를 분리할 수 있다.
이터레이터 객체와 반복 대상인 객체를 합쳐서 객체 자체를 이터레이터로 만들면 더 간단하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
let range = { from: 1, to: 5 [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; for (let num of range) { alert(num); // 1, 2, 3, 4, 5 }
이러면
range[Symbol.iterator]()
는 객체range
자체를 반환한다. 이 방식의 단점은 두 개의for..of
반복문을 하나의 객체에 동시에 사용할 수 없다는 점이다. 이터레이터(객체 자신)가 하나뿐이어서 두 반복문이 반복 상태를 공유하기 때문이다.메서드
Symbol.iterator
는for..of
에 의해 자동으로 호출되는데, 개발자가 명시적으로 호출하는 것도 가능하다.1 2 3 4 5 6 7 8 9
let str = "Hello"; // for (let char of str) alert(char); 와 같음 let iterator = str[Symbol.iterator](); while (true) { let result = iterator.next(); if (result.done) break; alert(result.value); // H, e, l, l, o }
이런 경우는 거의 없지만,
for..of
보다 반복 과정을 더 잘 통제할 수 있다는 장점이 있다. 반복을 시작했다가 잠시 멈춰 다른 작업을 하다가 다시 반복을 시작하는 등 반복 과정을 여러개로 쪼갤 수 있다.문자열이나 배열 같은 내장 이터러블에도
Symbol.iterator
가 구현되어 있다.문자열 이터레이터는 서로게이트 쌍을 지원한다.
인덱스와
length
프로퍼티가 있는 객체는 유사 배열이라고 한다. 유사 배열 객체에는 다양한 프로퍼티와 메서드가 있을 수 있는데, 배열 내장 메서드는 없다.이터러블 객체라고 해서 유사 배열 객체는 아니다. 유사 배열 객체라고 해서 이터러블 객체인 것도 아니다.
range
는 이터러블 객체이지만 인덱스도 없고length
프로퍼티도 없으므로 유사 배열 객체가 아니다.1 2 3 4 5 6 7 8
let arrayLike = { 0: "Hello", 1: "World", length: 2 } // Symbol.iterator가 없으므로 에러 발생 for (let item of arrayLike) {}
arrayLike는 유사 배열이지만 이터러블 객체가 아니다.
대부분의 메서드는 ‘진짜’ 배열이 아닌 이터러블이나 유사 배열을 대상으로 동작한다고 쓰여있는 걸 명세서에서 볼 수 있다. 이 방법이 더 추상적이기 때문이다.
Array.from(obj[, mapFn, thisArg])
을 사용하면 이터러블이나 유사 배열인obj
를 진짜Array
로 만들 수 있다. 이렇게 하면obj
에도 배열 메서드를 사용할 수 있다. 선택 인수mapFn
와thisArg
는 각 요소에 함수를 적용할 수 있게 해준다.1 2 3
let arr = Array.from(range, num => num * num); alert(arr); // 1, 4, 9, 16, 25