본문 바로가기
Frontend/JavaScript

[JavaScript] 이터러블

by 모너아링 2023. 2. 16.

이터러블

스프레드 문법

  • 하나로 뭉쳐 있는 여러 값들의 집합을 전개하여 개별적인 값들의 목록으로 만드는 것
// ... [1, 2, 3]은 [1, 2, 3]을 개별 요소로 분리한다.(-> 1, 2, 3)
console.log(...[1, 2, 3]); // 1 2 3
consolg.log(...'Hello'); // H e l l o

배열 디스트럭처링 할당(구조 분해 할당)

  • 구조화된 배열과 같은 이터러블 또는 객체를 destructuring 하여 1개 이상의 변수에 개별적으로 할당하는것
const arr = [1, 2, 3];
//1, 2, 3을 각각 one, two, three에 개별적으로 할당
const [one, two, three] = arr;

console.log(one, two, three); // 1 2 3

이터레이션 프로토콜

  • 순회 가능한 데이터 컬렉션을 만들기 위해 정의한 규칙
  1. 이터러블 프로토콜
    • Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환하는 것
    • 즉, 이터레이터를 반환하는 **Symbol.iterator 메서드** 구현되어 있어야 함
  2. 이터레이터 프로토콜
    • 이터러블의 Symbol.iterator 메서드를 호출 시 이터레이터 반환
    • 이터레이터의 next 메서드를 호출하면 이터레이터 리절트 객체를 반환
    • 즉, 이터레이터 리절트 객체를 반환하는 **next 메서드** 구현되어 있어야 함

이터러블

  • 이터러블 프로토콜을 준수한 객체
  • 반복 가능한 객체
  • Symbol.iterator 를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체
//배열, 문자열, Map, Set 은 이터러블
isIterable([]); // true
isIterable(''); // true
isIterable(new Map()); // true
isIterable(new Set()); // true

//일반 객체는 이터러블이 아님
isIterable({}); // false
  • for ... of 문으로 순회할 수 있으며 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용 가능
//**배열**
const array = [1, 2, 3];

// 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블
console.log(Symbol.iterator in array); // true

for(const item of array) {
    console.log(item); // 1 2 3
}

console.log([...array]); // [1, 2, 3]
const [a, ...rest] = array;

console.log(a, rest); // 1, [2, 3]
//문자열
const string = 'String';

console.log(Symbol.iterator in string); // TypeError(???)

for (const item of string) {
    console.log(item); // S t r i n g
}

console.log([...string]); // [ 'S', 't', 'r', 'i', 'n', 'g' ]
const [a, ...rest] = string;

console.log(a, rest); // S [ 't', 'r', 'i', 'n', 'g' ]
//**Map**
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);

console.log(Symbol.iterator in map); // true

for (const [key, value] of map) {
    console.log(`${key} ${value}`); // a 1  b 2  c 3
}

console.log([...map]); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
const [a, ...rest] = map;

console.log(a, rest); // [ 'a', 1 ] [ [ 'b', 2 ], [ 'c', 3 ] ]
//**Set**
const set = new Set([1, 2, 3]);

console.log(Symbol.iterator in set); // true

for (const value of set) {
    console.log(value); // 1 2 3
}

console.log([...set]); // [1, 2, 3]
const [a, ...rest] = set;

console.log(a, rest); // 1 [2, 3]
//**일반 객체**
const obj = { a: 1, b: 2 };

//Symbol.iterator 메서드를 구현하거나 상속받지 않는다.
//이터러블 프로토콜을 준수한 이터러블이 아니다.
console.log(Symbol.iterator in obj); // false

//for ... of 사용 불가
for(const item of obj){ // TypeError
    console.log(item);
}

//배열 디스트럭처링 할당 사용 불가
const [a, b] = obj; // TypeError

//**스프레드 문법 사용 가능**
console.log({ ...obj}); // { a: 1, b: 2 }

이터레이터

  • 값을 차례대로 꺼낼 수 있는 객체
  • 이터러블 요소를 탐색하기 위한 포인터의 역할
  • 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수하며 **next 메서드를 갖는** 이터레이터를 반환
  • next 메서드를 호출하면 이터러블을 순차적으로 한 단계씩 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를 반환

이터레이터 리절트 객체

const array = [1, 2, 3];

//Symbol.iterator 메서드 호출 방법
//이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();

//Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다.
console.log('next' in iterator); // true

//next 메서드를 호출하면 이터레이터 리절트 객체를 반환
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
  • value
    • 현재 순회 중인 이터러블의 값
  • done
    • 이터러블의 순회 완료 여부

이터레이터 vs 배열

//배열
const array = [1, 2, 3, 4, 5, 6, 7];
//array[0] == 1, array[3] == 4
//array.length == 7

//이터레이터
const iterator = (function() {
    let num = 1;

    return {
        next: function() {
            return (
                num > 7 ?
                { done: true } :
                { done: false, value: num++ }
            );
        }
    };
})();
//오직 next() 메소드만이 존재
//done 과 value 항목을 담은 리절트 객체 반환
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
  • 배열
    • 랜덤 액세스 가능
    • 이터레이터의 기능을 포함
  • 이터레이터
    • 이전에 얻어온 값 바로 다음 값만 가져올 수 있음
    • 배열의 map() 메소드와 비슷한 기능

기능이 한 가지밖에 없는 이터레이터가 존재하는 이유는?

→ 높은 가독성, 메모리 효율적 사용

빌트인 이터러블

  • 이터레이션 프로토콜을 준수한 객체
빌트인 이터러블 Symbol.iterator 메서드
Array Array.prototype[Symbol.iterator]
String String.prototype[Symbol.iterator]
Map Map.prototype[Symbol.iterator]
Set Set.prototype[Symbol.iterator]
TypedArray TypedArray.prototype[Symbol.iterator]
arguments arguments[Symbol.iterator]
DOM 컬렉션 NodeList.prototype[Symbol.iterator] / HTMLCollection.prototype[Symbol.iterator]

for … of 문

  • 이터러블을 순회하면서 이터러블의 요소를 변수에 할당
for (변수선언문 of 이터러블) { ... }
  1. 이터레이터의 next 메서드 호출하여 이터러블 순회
  2. 이터레이터 리절트 객체의 value 프로퍼티 값을 for ... of 문의 변수에 할당
    1. done 프로퍼티 값이 false이면 1번으로
    2. done 프로퍼티 값이 true 이면 순회 중단
const iterable = [1, 2, 3];

//for 문
//이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성
const iterator = iterable[Symbol.iterator]();

for(;;){
    //next 메서드는 이터레이터 리절트 객체 반환
    const res = iterator.next();

    if(res.done) break;
    const item = res.value;
    console.log(item); // 1 2 3
}

//for...of 문
for(const item of [1, 2, 3]) {
    console.log(item); // 1 2 3
}
  • next 메서드 호출 시
    • { value: 1, done: false }
      • res.done === false 이므로 계속 순회, item 은 1
    • { value: 2, done: false }
      • res.done === false 이므로 계속 순회, item 은 2
    • { value: 3, done: false }
      • res.done === false 이므로 계속 순회, item 은 3
    • { value: undefined, done: true }
      • res.done === true 이므로 순회 종료

이터러블과 유사 배열 객체

  • 유사 배열 객체는 이터러블이 아닌 일반 객체
//유사 배열 객체
const arrayLike = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
};

//for...of 문 사용 불가
for(const item of arrayLike) {
    console.log(item); //TypeError: arrayLike is not iterable
}

//배열 디스트럭처링 할당 사용 불가
const [a, b, c] = arrayLike; //TypeError: arrayLike is not iterable

//**스프레드 문법 사용 가능**
console.log({ ...arrayLike }); // { '0': 1, '1': 2, '2': 3 }

//**Array.from**은 유사 배열 객체 또는 이터러블을 배열로 변환
const arr = Array.from(arrayLike);

이터레이션 프로토콜의 필요성

  • ES6 이전의 순회 가능한 데이터 컬렉션은 통일된 규약 없이 나름의 구조를 가지고 순회할 수 있었음
  • ES6에서는 이들을 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 일원화하였다.

Untitled

  • 이터레이션 프로토콜은 데이터 소비자와 데이터 공급자를 연결하는 인터페이스 역할을 한다.

사용자 정의 이터러블

  • 이터러블이 아닌 일반 객체를 이터레이션 프로토콜을 준수하도록 구현한 것

사용자 정의 이터러블 구현

const fibonacci = {
        //Symbol.iterator 메서드를 구현
    [Symbol.iterator](){
        let [pre, cur] = [0, 1];
        const max = 10;

                //Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터 반환
        return {
            next(){
                [pre, cur] = [cur, pre + cur];
                                //next 메서드는 이터레이터 리절트 객체 반환
                return { value: cur, done: cur >= max };
            }
          };
    }
};
for (const num of fibonacci) {
    console.log(num); // 1 2 3 5 8
}
  1. Symbol.iterator 메서드를 구현
  2. Symbol.iterator 메서드가 next 메서드를 갖는 이터레이터 반환
  3. next 메서드는 이터레이터 리절트 객체 반환

이터러블을 생성하는 함수

  • 고정적이었던 값을 인수로 전달받아 이터러블을 반환하는 함수를 생성
//수열의 최대값을 인수로 전달받음
const fibonacciFunc = function (max) {
        let [pre, cur] = [0, 1];

        //Symbol.iterator 메서드를 구현한 이터러블 반환
    return {
                [Symbol.iterator]() {
                        return {
                        next(){
                            [pre, cur] = [cur, pre + cur];
                                        //next 메서드는 이터레이터 리절트 객체 반환
                            return { value: cur, done: cur >= max };
                        }
                    };
                }
        };
};
for (const num of fibonacci) {
    console.log(num);
}

이터러블이면서 이터레이터인 객체를 생성하는 함수

//1. next 메서드 구현 => 이터레이터의 조건
{
        next: () => {}
}

//2. [Symbol.iterator]라는 이름의 iterator 구현 => 이터러블의 조건
{
        next: () => {},
        [Symbol.iterator]: function() {}
}
//3. [Symbol.iterator]는 iterator 로서 구현되어야 하기 때문에 next 메서드 필요
{
        next: () => {},
        [Symbol.iterator]: function() {
                return this;    
        }
}
//이터러블이면서 이터레이터인 객체
//이터레이터를 반환하는 Symbol.iterator 메서드와 이터레이션 리절트 객체를 반환하는 next 메서드 소유
{
        [Symbol.iterator]() { return this; }
        next() {
                return { value: any, done: boolean }
        }
}
// 이터러블이면서 이터레이터인 객체를 반환하는 함수
const fibonacciFunc = function (max) {
  let [pre, cur] = [0, 1];

  // Symbol.iterator 메소드와 next 메소드를 소유한
  // 이터러블이면서 이터레이터인 객체를 반환
  return {
    // Symbol.iterator 메소드
    [Symbol.iterator]() {
      return this;
    },
    // next 메소드는 이터레이터 리절트 객체를 반환
    next() {
      [pre, cur] = [cur, pre + cur];
      return {
        value: cur,
        done: cur >= max
      };
    }
  };
};

// iter는 이터러블이면서 이터레이터이다.
let iter = fibonacciFunc(10);

// iter는 이터레이터이다.
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: 5, done: false}
console.log(iter.next()); // {value: 8, done: false}
console.log(iter.next()); // {value: 13, done: true}

iter = fibonacciFunc(10);

// iter는 이터러블이다.
for (const num of iter) {
  console.log(num); // 1 2 3 5 8
}

무한 이터러블과 지연 평가

// 무한 이터러블을 생성하는 함수
const fibonacciFunc = function () {
  let [pre, cur] = [0, 1];

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      [pre, cur] = [cur, pre + cur];
      // done 프로퍼티를 생략한다.
      return { value: cur };
    }
  };
};

// fibonacciFunc 함수는 무한 이터러블을 생성한다.
for (const num of fibonacciFunc()) {
  if (num > 10000) break;
  console.log(num); // 1 2 3 5 8...
}

// 배열 디스트럭처링 할당 가능
// 무한 이터러블에서 3개만을 취득한다.
const [f1, f2, f3] = fibonacciFunc();
console.log(f1, f2, f3); // 1 2 3
  • 이터러블은 값을 생성하는데, 지연 평가를 통해 평가를 늦춘다.
  • 위 함수는 무한 이터러블 생성 ⇒ 데이터 소비자 등장 전까지는 데이터를 생성하지 않음
  • 데이터가 필요할 때까지 생성을 지연하다가 필요한 순간 생성

'Frontend > JavaScript' 카테고리의 다른 글

[JavaScript] 제너레이터와 async/await  (0) 2023.02.16
[JavaScript] DOM 이벤트  (0) 2023.02.16
[JavaScript] Set  (2) 2023.02.16
[JavaScript] 함수와 일급 객체  (0) 2022.11.08
[JavaScript] 객체 리터럴  (1) 2022.11.08