이터러블

Taemin Jang·2024년 6월 13일
0

Javascript

목록 보기
9/14

이터레이션 프로토콜 (Iteration protocols)

이터레이션 프로토콜은 내장 객체 또는 구문이 아닌 프로토콜로써 몇가지 규칙에 따라 모든 객체에서 구현될 수 있다.

ES6에서 순회 가능한(Iterable) 자료 구조를 만들기 위해 도입 됐다.

이터레이션 프로토콜에는 이터러블 프로토콜과 이터레이터 프로토콜이 있다.

이터러블 프로토콜 (Iterable protocol)

이터러블은 for ... of문으로 순회할 수 있으며 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.

이터러블은 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속 받은 Symbol.iterator메서드를 호출하여 구현할 수 있다.

이터레이터 프로토콜 (Iterator protocol)

이터러블의 Symbol.iterator메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환한다.

이터레이터는 next 메서드를 가지고 있어 호출하면 이터러블을 순회하고, value와 done 프로퍼티를 갖는 이터레이터 result 객체를 반환한다.

  • value는 현재 순회중인 이터러블의 값을 반환
  • done은 이터러블의 순회 완료 여부를 반환

즉, 이터레이터는 이터러블의 요소를 탐색하기 위한 포인터 역할을 한다.

for ... of문은 [Symbole.iterator]() 메소드의 호출로 시작해서 이터러블 값을 next()를 반복적으로 호출하여 순회한다. 순회가 끝나면 done: true를 반환한다.

[Symbol.iterator]()

// 아래 처럼 next 메소드를 통해 이터러블 요소를 탐색하기 위한 포인터 되어있다.
{
  next() {
    return { value: any, done: boolean }
  }
}

// 배열은 이터러블이다.
const array = [1, 2, 3];

// 이터러블은 Symbol.iterator 메소드를 소유한다.
// Symbol.iterator 메소드는 이터레이터를 반환한다.
let iter = array[Symbol.iterator]();

// 이터레이터는 next 메소드를 소유한다.
// next 메소드는 이터레이터 result 객체를 반환한다.
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: undefined, done: true}

[Symbol.iterator]

Symbol.iterator를 프로퍼티 key로 사용한 메소드를 가진 객체는 이터러블이다.

const iterable1 = {};

iterable1[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

console.log([...iterable1]); // [1, 2, 3]

.iterator()가 아닌 [Symbol.iterator] 메소드를 사용하는 이유

기존에 사용 중인 객체가 이미 .iterator() 메소드를 갖고 있다면 문제가 될 수 있다.

따라서 평범한 문자열 대신 Symbol을 사용하여 고유한 메소드를 정의해준 것이다.

const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';

// 배열, 문자열, Map, Set 등은 이터러블이다.
isIterable([]); // true
isIterable(''); // true
isIterable(new Map()); // true
isIterable(new Set()); // true
isIterable({}); // false

빌트인 이터러블

자바스크립트는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공한다.

빌트인 이터러블Symbol.iterator 메서드
ArrayArray.prototype[Symbol.iterator]
StringString.prototype[Symbol.iterator]
MapMap.prototype[Symbol.iterator]
SetSet.prototype[Symbol.iterator]
TypeArrayTypeArray.prototype[Symbol.iterator]
argumentsarguments[Symbol.iterator]
DOM 컬렉션NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator]

for ... of 문

for (변수 선언문 of 이터러블) { ... }이와같이 사용할 수 있다.

// 배열은 이터러블
for (const item of [1, 2, 3]) {
  // item 변수에 순차적으로 1, 2, 3이 할당 된다.
  console.log(item); // 1 2 3
}

// 위 동작을 for문으로는 다음처럼 표현할 수 있다.
const iterable = [1, 2, 3];

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

for ( ;; ) {
  // next 메서드를 호출하여 이터러블을 순회한다.
  const res = iterator.next();
  
  // result 객체의 done 프로퍼티 값이 true이면 이터러블 순회를 중단한다.
  if (res.done) break;
  
  const item = res.value;
  console.log(item); // 1 2 3
}

이터러블과 유사 배열 객체

유사 배열 객체는 배열처럼 인덱스로 프로퍼티 값에 접근 가능하고 length 프로퍼티를 갖는 객체를 말한다.

유사 배열 객체도 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있고, 인덱스를 나타내는 숫자 형식의 문자열 프로퍼티를 키로 가지고 있기 때문에 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있다.

// 유사 배열 객체
const arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  length: 3
}

// 유사 배열 객체는 length 프로퍼티를 갖기 때문에 for문으로 순회할 수 있다.
for (let i = 0; i < arrayLike.length; i++) {
  console.log(arrayLike[i]); // 1 2 3
}

하지만 유사 배열 객체는 이터러블이 아는 일반 객체다.

// Symbol.iterator 메서드가 없기 때문에 for ... of 문으로 순회할 수 없다.
for (const item of arrayLike) {
  console.log(item); // 1 2 3
} // TypeError: arrayLike is not iterable

하지만 arguments, NodeList, HTMLCollection은 유사 배열 객체이면서 이터러블이다.

이유는 ES6에서 이터러블이 도입되면서 해당 객체에 Symbol.iterator 메서드를 구현했기 때문에 이터러블이 되었다.

// 유사 배열 객체
const arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  length: 3
}

// Array.from을 통해 유사 배열 객체를 배열로 변환할 수 있다.
const arr = Array.from(arrayLike);
console.log(arr); // [1, 2, 3]

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

사용자 정의 이터러블

일반 객체에 이터레이션 프로토콜을 준수하도록 구현하면 사용자 정의 이터러블이 된다.

// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = {
  // Symbol.iterator 메서드를 구현하여 이터러블 프로토콜 준수
  [Symbol.iterator]() {
    let [pre, cur] = [0, 1];
    const max = 10;
    
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { value: cur, done: cur >= max }
      }
    }
  }
}

for (const num of fibonacci) {
  console.log(num); // 1 2 3 5 8
}

const arr = [...fibonacci];
console.log(arr); // [1, 2, 3, 5, 8]

const [first, second, ...rest] = fibonacci;
console.log(first, second, rest); // 1 2 [3, 5, 8]

만들어진 사용자 정의 이터러블을 for ... of 문으로 순회할 때마다 next 메서드가 호출되고, done 프로퍼티가 true 값을 가질 때 까지 반복한다.

이처럼 이터러블은 for ... of 문뿐만 아니라 스프레드 문법, 배열 디스트럭처링 할당에도 사용 가능하다.

max는 10으로 고정되어 있는데 이를 인수로 전달받아 이터러블을 반환하는 함수로 만들 수 있다.

// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = function (max) {
  let [pre, cur] = [0, 1];
  // Symbol.iterator 메서드를 구현하여 이터러블 프로토콜 준수
  return {
  	[Symbol.iterator]() {    
      return {
      	next() {
          [pre, cur] = [cur, pre + cur];
          return { value: cur, done: cur >= max }
        }
      }
    }
  }
}

for (const num of fibonacci(10)) {
  console.log(num); // 1 2 3 5 8
}
profile
하루하루 공부한 내용 기록하기

0개의 댓글

관련 채용 정보