본문 바로가기
C++ 200제/코딩 IT 정보

TypeScript 강좌 15. 복합형 for of 및 iterable 반복자

by vicddory 2020. 2. 18.

루프는 for ... of 사용

루프 작성 크게 3가지 방법이 있습니다. C언어에서 유래한 루프는 예전부터 존재했고 루프 사용 시 변수가 필요합니다.


Typescript forEach()는 ES5에서 추가되었고, 언어 사양의 업데이트와 함께 for ... of 구문도 추가되었습니다. 이 구문은 Array, Set, Map, String 등의 반복 가능한(iterable) 객체(오브젝트)를 대상으로 루프가 돕니다. 배열의 경우 인덱스 값이 필요한 경우 entries() 메소드를 사용합니다.

타입스크립트 코드를 함수형 스타일로 통일하기 위해 for ... of를 금지하고, forEach()만 사용한다는 코딩 표준을 정하는 회사도 있습니다(Airbnb).


var iterable = ["김일성", "원균", "기황후"];

// 예전 스타일 C언어 루프
for (var i = 0; i < iterable.length; i++) {
    var value = iterable[i];
    console.log(value);
}

// 중간 단계 forEach 루프
iterable.forEach(value => {
  console.log(value);
});

// 새로운 방법 : for of 루프로 배열 인덱스 기준으로 순환합니다.
for (const [i, value] of iterable.entries()) {
  console.log(i, value);
}

// 요소만을 원할 땐 for (const value of iterable)


주석

entries() 함수는 출력 대상을 ES2015 이상이어야 작동합니다. 그렇지 않으면 아래와 같은 오류가 발생합니다.


// error TS2339: Property 'entries' does not exist on type 'string[]'.


Polyfill을 사용하여 해결할 수 있지만, Polyfill 없이 대처할 수 있는 방법은 forEach() 사용(두 번째 인수가 인덱스) 또는 기존 루프 사용입니다.


속도면에선 기존의 for 루프가 가장 빠릅니다. for ... of 및 forEach()는 루프가 한 번 돌 때마다 함수 호출이 발생해 비용이 다소 증가합니다. 그렇다고 해도 게임의 좌표 계산에서 1프레임마다 수만 요소의 루프를 돌리는 케이스 외엔 거의 영향이 없습니다. 되도록 for ... of 루프를 사용하도록 합시다.



iterable와 반복자

앞서 entries() 메소드가 나왔습니다. 루프가 한 번 돌 때마다 인덱스와 튜플 데이터의 반복자(이터레이터)를 돌려줍니다. 배열 루프의 경우, 인덱스와 값을 함께 반환할 때 이 반복자를 사용합니다.


const a = ["a", "b", "c"];
const b = [[0, "a"], [1, "b"], [2, "c"]];

// 2개의 결과는 똑같음
for (const [i, v] of a.entries()) { console.log(i, v); }
for (const [i, v] of b) { console.log(i, v); }


이 entries()는 누구인가요? 정답은 next()라는 메소드를 보유한 반복자 개체 반환 메서드입니다. next() 배열의 요소와 종료 여부를 나타내는 boolean 값을 돌려줍니다. 타입스크립트 이터레이터(엄밀하게는 외부 반복자)는 Java, Python, C++에서는 친숙한 키워드입니다.


위의 b처럼 모든 요소를 이중 배열로 만들면 이터레이터라는 것이 필요 없지만, 이 경우 요소 수가 많을수록 복사 시간이 오래 걸려 루프를 돌기 전에 준비하는 단계가 느리다는 단점이 있습니다. 따라서 이 반복자는 요소를 반환하기 전 전체 복사를 막아줍니다.

오브젝트에 루프 요소를 꺼내는 메소드(@@iterator)를 보유한 객체는 iterable입니다. 반복 처리에 대한 약속은 이미 정해져있으므로 "iterable 프로토콜"이라고도 합니다. 이 메소드는 반복자(이터레이터 iterator)를 돌려줍니다. 배열은 @@iterator이외에도 keys(), values(), entries()와 반복자를 리턴하는 메소드 총 4개 있습니다.


for...of 루프 등은 이 프로토콜에 따라 루프를 수행합니다. 이외에도 분할 할당(분할 대입), 스프레드 구문 등 여기서 소개한 기능은 iterable 프로토콜을 바탕으로 제공됩니다.


Array, Set, Map, String 등의 객체가 이 프로토콜을 제공하며, 앞으로 나오는 데이터 구조도 이 프로토콜을 지원합니다.


반복자는 루프를 돌 때 문제없지만, 원하는 위치의 요소에 액세스(접근)하는 제어 등은 불편합니다. 타입스크립트 이터레이터에서 배열로 변환하려면 Array.from() 메소드 또는 스프레드 구문을 사용합니다.


const names = Array.from(iterable);

// 이런 방법도 가능
const names = [...iterable];


주석

반복자(이터레이터)는 ES2015 이후에만 존재합니다. 따라서, 스프레드 구문을 사용하여 이터레이터를 배열로 변환하는 출력 대상이 ES2015 이상이어야 합니다.


const names = [...iterable];


for문의 기본적인 구동 원리for문의 기본적인 구동 원리


TypeScript와 배열 

for ... of는 속도에 단점이 있다고 소개했습니다. 그러나 TypeScript에선 약간의 이점이 있습니다.


TypeScript를 사용하고 ES5로 출력하는 경우 : Array 형태 for ... of 루프는 기존의 빠른 for 루프 JavaScript 코드가 생성되므로, 속도상의 페널티가 전혀 없습니다. 또한, Chrome 등 JavaScript 엔진은 같은 형태의 요소만 보유한 배열의 경우 특별한 최적화를 실시합니다.


타입스크립트를 사용하면 형식 정보가 붙어 쉽게 구현할 수 있을 뿐만 아니라 속도상 장점도 있습니다.



미완성 강좌 문서입니다 ...

댓글