[SW]/JavaScript (2025)

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

시원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