상세 컨텐츠

본문 제목

[자바스크립트] 섹션 10. 이터러블과 제너레이터

[SW]/JavaScript (2025)

by 시원00 2025. 4. 13. 14:15

본문

728x90

제대로 파는 자바스크립트(JavaScript) - by 얄코

(https://www.inflearn.com/course/제대로-파는-자바스크립트/dashboard)

섹션 10. 이터러블과 제너레이터

더보기
목차
10-1. Set
10-2. Map
10-3. 이터러블
10-4. 제너레이터
10-5. 중간점검 퀴즈

10-1. Set

중복되지 않는 값들의 집합

표준 내장 객체 중 하나

 

배열과의 차이

- 동일한 값을 여러 번 포함할 수 없음

- 값들의 순서가 무의미함

 

I. 기본 사용법

 

1. has 메서드: 요소 포함 여부 확인

 

2. delete 메서드: 요소 제거 + 성공 여부 반환

 

3. add 메서드: 결과 set을 반환 (∴ 메서드 체이닝이 가능)

 

4. size 프로퍼티: 요소의 개수

 

5. keys(), values(), entries() 메서드: 값, 값, [값, 값] 반환

key를 value와 같이 취급 (순서가 의미 없음)

 

6. clear 메서드: 모든 요소들을 삭제

 

참조형 데이터의 경우

내용이 같더라도 참조하는 주소가 다르므로 각기 다른 것으로 인식

참조하는 주소가 같으면 같은 것들로 인식

 

II. 이터러블로서의 Set

 

1. for ... of 문

 

2. 스프레드 문법

 

3. 디스트럭쳐링

 

(이터러블과 별개로) forEach 메서드 사용 가능

Set은 배열과 달리 순서 개념이 없으므로 두 번째 인자가 인덱스가 아님(형식을 맞추기 위한 인자로 현재값 사용)

(참고) 7-3 forEach 설명 (https://siwon-swu18.tistory.com/87)


10-2. Map

키와 값의 쌍으로 이루어진 컬렉션

표준 내장 객체 중 하나

 

객체와의 차이

이터러블의 일종이므로 이터러블의 기능 사용 가능

메서드와 프로퍼티 등 기능 차이

객체나 배열 등의 참조값을 키로 사용 가능

키와 값을 자주 변경하는 경우 적합하도록 설계됨

 

I. 기본 사용법

 

키의 중복 불허: 해당 키가 있으면 덮어씀

 

1. has 메서드: 요소 포함 여부 확인

 

2. get 메서드: 값에 접근

 

참조값도 키로 사용 가능

객체와 다르게 '참조값'이 키이기 때문에 다른 객체로는 접근 불가

 

(참고) 5-1의 객체와 비교 (https://siwon-swu18.tistory.com/87)

 

3. delete 메서드: 요소 제거 + 성공 여부 반환

 

4. set 메서드: 요소 추가 + 결과 맵을 반환

결과 맵을 반환하므로 메서드 체이닝이 가능

 

5. size 프로퍼티: 요소의 개수

 

6. keys, values, entries 메서드: 키, 값, [키, 값] 반환

 

7. clear 메서드: 모든 요소들을 삭제

 

II. 이터러블로서의 Map

 

1. for ... of 문

 

2. 스프레드 문법

 

3. 디스트럭쳐링

 

(이터러블과 별개로) forEach 메서드 사용 가능


10-3. 이터러블

I. 이터러블 프로토콜 iterable protocol

반복, 순회 기능을 사용하는 주체간의 통일된 규격

공통 기능들: for ... of, 스프레드 문법, 배열 디스트럭쳐링

 

이터러블 iterable: 이터러블 프로토콜을 준수하는 객체

배열, 문자열, Set, Map, arguments 등

키 Symbol.iterator(well-known 심볼)의 값으로 이터레이터를 반환하는 메서드를 가짐

 

Symbol.iterator() 실행 시 이터레이터 반환

 

II. 이터레이터 iterator

next 메서드를 통해 이터러블을 순회하며 값을 반환

next 메서드 내 프로토콜

- value: 해당 차례에 반환할 값

- done: 순회 종료 여부(마지막 값 반환 다음 차례부터)
   *순회가 끝나면 value는 undefined, done은 true 반환

 

III. 이터러블 만들어보기

예제 1. 주사위를 열 번 굴리는 이터러블

순회 최댓값(maxCount)을 10으로 설정(이후로는 done: true)

 

이터러블의 공통 기능들 사용 가능

1. for ... of

2. 스프레드 문법

 

(참고) 스프레드는 어떻게 작동할까?

const arr = [...iterable];

위의 코드는 내부적으로 아래와 같은 동작을 함

const iterator = iterable[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
    arr.push(result.value);
    result = iterator.next();
}

즉, diceTenTimes에서 Symbol.iterator()를 호출 → 내부의 next()를 계속 호출 → 나온 value를 배열에 넣음 → done: true일 때까지 반복하는 것

 

(참고) diceTenTimes는 "이터러블"이다.

diceTenTimes는 Symbol.iterator 메서드를 구현해서 이터러블 프로토콜을 따르는 일반 객체이다.

이터러블이라는 건 "값을 하나씩 꺼낼 수 있다"는 능력만 있는 것이지, 내부에 배열처럼 값을 저장하고 있는 건 아니다. 따라서 for...of, [...], Array.from() 등을 통해 값을 순회해서 수집해야만 실제 결과를 얻을 수 있다.

 

3. 배열 디스트럭쳐링

 

예제 2. 피보나치 이터러블

 

원하는 최대 횟수의 피보나치 이터러블 생성하기

 

예제 3. 순번 이터러블 X 이터레이터

이터러블의 역할도 하는 이터레이터 만들기

 

이터레이터를 겸하는 경우 한 번 순회하면 끝

상태(idx)가 객체에 고정되어있기 때문에 여러 번 반복하면 의도치 않은 결과가 나올 수 있음

새로 생성해야 다시 순회 가능

 

이터레이터로 사용

이터러블을 반복할 수 있는 이터레이터를 직접 인자로 전달

 

(참고) fiboIterator()도 이터레이터이므로 인자로 받을 수 있음


10-4. 제너레이터

제너레이터 generator

함수 내 코드들을 모두 실행하지 않고 외부 호출자에게 제어권을 양도

이터러블과 이터레이터를 간결하게 구현 가능

 

I. 기본 사용법

1. 제너레이터 함수/메서드 선언

function 다음 또는 메서드명 앞에 * (띄어쓰기 위치 무관)

yield 반환값;

 

2. 제너레이터 객체

제너레이터 함수의 결과값으로 반환

이터레이터이자 이터러블

로그에서 next와 Symbol(Symbol.iterator) 확인 가능

이터러블로서는 바로 호출해서 사용하는 것이 적합함

next 메서드를 실행하면 다음 yield까지 실행 후 중지

yield의 값을 value로 반환

끝까지 실행 후 done: true

 

II. 이터러블과 이터레이터 대체하기

예제 1. 주사위를 열 번 굴리는 제너레이터

 

예제 2. 피보나치 제너레이터

재생성 없이 다시 순회하면 {value: undefined, done: true} 반환

 

예제 3. 순번 제너레이터


10-5. 중간점검 퀴즈

1. 위 배열의 중복 요소들을 제거하고 가나다순으로 정렬한 뒤 쉼표로 구분하여 출력하는 코드를 작성해보세요.

const array = [
    '모', '걸', '도', '개', '윷',
    '윷', '걸', '모', '개', '도',
    '윷', '모', '걸', '도', '개',
    '윷', '모', '걸', '개', '도'
];

예상 답:

const set = new Set(array);
const sortArr = [...set].sort((a, b) => a > b ? 1 : -1);
console.log(sortArr.join(', '));

 

정답:

const yuts = [...new Set(array)]
    .toSorted()
    .join(', ');

console.log(yuts);

(참고) sort(), toSorted() : 정렬된 결과를 반환. 인자가 없으면 오름차순 정렬

   toSorted(): ES14 추가 기능, 원본 배열 수정X

 

2. 위 배열의 요소들이 각각 몇 개씩 들어있는지를 나타내는 Map 인스턴스를 만드는 코드를 작성해보세요.

그 중 'apple'은 몇 개인지 출력해보세요. 개수가 가장 적은 순으로 출력해보세요.

const array = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

예상 답:

const map = new Map();

for (item of array) {
    map.get(item) ? map.set(item, map.get(item)+1) : map.set(item, 1);
}

console.log(map);

console.log(map.get('apple'));

const arr = [...map]; 
arr.sort((a, b) => a[1] - b[1]);
arr.forEach( item => console.log(item));

 

정답: 

const countMap = new Map();

array.forEach(item => { // for .. of 대신 forEach 사용 
    countMap.set(item, (countMap.get(item) || 0) + 1); // set 안에서 || 사용하여 처리
});

console.log(countMap.get('apple'));

[...countMap]
    .toSorted((a, b) => a[1] - b[1]) // sort 대신 toSorted 사용
    .forEach(i => console.log(i));

 

3. 인자로 from과 to 이 두 숫자가 주어졌을 때 from으로부터 to까지 1씩 늘려가며 출력하는 이터러블을 반환하는 함수를 만들어보세요. 전자가 항상 더 작다고 가정합니다.

// 사용 예시
const iterable = makeIterable(1, 5);

for (let value of iterable) {
	console.log(value); // 1, 2, 3, 4, 5
}

예상 답:

function makeIterable(from, to) {
    return {
        [Symbol.iterator]() {
            return {
                next() {
                    return { value: from, done: from++ > to };
                }
            }
        }
    }
}

 

(참고) from과 to를 직접 사용

   단일 이터레이터만 사용할 경우에는 동작상 문제는 없지만

   여러 이터레이터를 동시에 생성하거나 재사용할 가능성이 있다면 문제가 생길 수 있음 (주의)

 

(참고) done이 true인 상태에서도 value에 마지막 값을 포함해서 반환한다는 점에서 일반적인 이터레이터의 규칙과는 어긋남

 

정답:

function makeIterable(from, to) {
    return {
        [Symbol.iterator]() {
            return {
                current: from,
                last: to,
// current와 last라는 지역 상태를 내부에 유지
// 이터레이터가 생성될 때마다 current가 독립적으로 존재하므로 동시 여러 이터레이터 생성 시에도 각각 정상 작동

                next() {
                    // done이 false인 경우와 true인 경우를 나눠서 작성
                    if (this.current <= this.last) {
                        return { done: false, value: this.current++ };
                    } else {
                        return {done: true };
                    }
                }
            };
        }
    };
}

(참고) 이터레이터는 done: true일 때 value를 포함하지 않는 것이 표준이다.

 

4. 3번 문제의 코드를 제너레이터로 작성해보세요.

// 사용 예시
const generator = makeGenerator(1, 5);
for (let value of generator) {
  console.log(value); // 1, 2, 3, 4, 5
}

예상 답:

function* makeGenerator(from, to) {
    while (from <= to) { // 제너레이터는 from과 to를 직접 사용해도 안전함 (아래 설명 참고)
        yield from++;
    }
}

 

정답:

function* makeGenerator(from, to) {
    for (let i = from; i <= to; i++) {
        yield i;
    }
}

 

(참고) 이터러블과 제너레이터 구현 차이(지역 변수)

- 이터러블 구현에서는 from을 직접 쓰지 말고, 복사해서 써야 한다.
   (이유: 이터레이터는 여러 번 호출될 수 있으므로 상태 충돌 방지 필요)

- 제너레이터는 내부 지역변수로 동작하므로 from을 직접 써도 안전하다.

   (제너레이터에서의 from은 함수 인자이자 지역 변수임)

   (제너레이터를 호출할 때마다 새로운 실행 컨텍스트가 생성되므로, 여러 제너레이터 인스턴스를 생성해도 상태가 충돌하지 않음)

 

 

 

FIN.

 

728x90

관련글 더보기

댓글 영역