Iterable객체

김하은·2023년 5월 16일
0

iterable객체 (반복 가능한 객체)는 배열을 일반화한 객체이다.
이터러블이라는 개념을 사용하게되면 어떤 객체에든 for...of반복문을 적용할 수 있다.

배열은 대표적인 이터러블이다.배열 외에도 다수의 내장 객체가 반복이 가능한다.
문자열 역시 이터러블의 예시 중 하나이다.

배열이 아닌 객체가 있는데 , 이 객체가 어떤 것들의 컬렉션을 나타내고 있는 경우, for...of문법을 적용할 수 있다면 컬렉션을 순회하는 데 유용할 것이다.

Symbol.iterator

직접 이터러블 객체를 만들어 보자.

let range = {
  from:1,
  to:5
}
// for(let num of range) ... 를 하면 num = 1,2,3,4,5 처럼 for...of가 동작하게

range를 이터러블로 만드려면(for...of로 동작하게 하려면) 객체에 Symbol.iterator(특수 내장 심볼)이라는 메서드를 추가해 어떠한 일이 발생하게 해야한다.

  1. for...of가 시작되자마자 for...of는 Symbol.iterator를 호출한다. 이것이 없으면 애러가 밯생한다.
    Symbol.iterator는 반드시 이터레이터(메서드 next가 있는객체)를 반환해야한다.

  2. 이후 for...of 는 반환된 객체(이터레이터)만을 대상으로 동작한다.

  3. for...of에 다음값이 필요하면, for...of는 이터레이터의 next()메서드를 호출한다.

  4. next()의 반환값은 {done: Boolean, value: any}와 같은 형태이어야 하고 done=true 는 반복이 종료되었음을 의미한다.
    false라면 value에 다음 값이 저장된다.

let range = {
  from: 1,
  to: 5
};

// 1. for..of 최초 호출 시, Symbol.iterator가 호출됩니다.
range[Symbol.iterator] = function() {

  // Symbol.iterator는 이터레이터 객체를 반환합니다.
  // 2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해집니다.
  return {
    current: this.from,
    last: this.to,

    // 3. for..of 반복문에 의해 반복마다 next()가 호출됩니다.
    next() {
      // 4. next()는 값을 객체 {done:.., value :...}형태로 반환해야 합니다.
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// 이제 의도한 대로 동작합니다!
for (let num of range) {
  alert(num); // 1, then 2, 3, 4, 5
}

이터러블 객체의 핵심은 관심사의 분리이다.

  • range에는 메서드 next()가 없다.
  • 대신 rangeSymbol.iterator를 호출해서 만든 이터레이터 객체와 이 객체의 메서드 next()에서 반복에 사용될 값을 만든다.
    아예 반복 대상 객체와 이터레이터 객체를 합치면 더 간단히 만들 수 있다.
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from; // current는 이함수를 감싸는 객체의 from이라는 키
    return this;
  },

  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  alert(num); // 1, then 2, 3, 4, 5
}

이렇게 작성하는 게 좋을 때가 종종 있습니다.

단점은 두 개의 for..of 반복문을 하나의 객체에 동시에 사용할 수 없다는 점이다.
이터레이터(객체 자신)가 하나뿐이어서 두 반복문이 반복 상태를 공유하기 때문.
그런데 동시에 두 개의 for..of를 사용하는 것은 비동기 처리에서도 흔한 케이스는 아니다.

next에는 제약이 없다. next가 값을 계속 반환하는 것은 정상적인 동작이다.
물론 위와같이 이터러블에 for...of반복문을 사용하면 끝이 없겠으나 break를 사용한다면 언제든 반복을 멈출 수 있다.

문자열은 이터러블

배열과 문자열은 가장 광범위하게 쓰이는 내장 이터러블이다.
for...of는 문자열의 각 글자를 순회한다.

for (let char of "test") {
  // 글자 하나당 한 번 실행됩니다(4회 호출).
  alert( char ); // t, e, s, t가 차례대로 출력됨
}

서로게이트 쌍에도 잘 작동한다.

let str = '𝒳😂';
for (let char of str) {
    alert( char ); // 𝒳와 😂가 차례대로 출력됨
}

이터레이터를 명시적으로 호출하기

이터레이터를 어떻게 명시적으로 사용할 수 있을까.

for...of를 사용했을 때와 동일한 방법으로 문자열을 순회하는 데 이번에는 직접호출해 순회해보자.
문자열 이터레이터를 만들고 값을 수동으로 가져온다.

let str = "Hello";

// for..of를 사용한 것과 동일한 작업을 합니다.
// for (let char of str) alert(char);

let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value); // 글자가 하나씩 출력됩니다.
}

이터레이터를 명시적으로 호출하는 경우는 거의 없으나 이 방법을 사용하면 for...of를 사용하는 것 보다 반복과정을 더 잘 통제할 수 있다.
반복을 시작했다가 잠시 멈추고 다른 작업을 하다가 다시 반복을 시작하는 등 반복과정을 여러개로 쪼개는 것이 가능하다.

이터러블과 유사배열

비슷해 보이나 아주 다른 용어 두가지.

  • 이터러블: 메서드 Symbol.iterator이 구현된 객체
  • 유사배열: 인덱스와 length프로퍼티가 있어 배열처럼 보이는 객체

브라우저 등의 호스트 환경에서 자바스크립트를 사용해 문제를 해결할 때 이터러블 객체나 유사배열 객체 혹은 둘 다인 객체를 만날 수 있다.

이터러블 객체이면서 유사배열객체인 문자열이 대표적인 예시이다.
이터러블 객체라고 다 유사배열 객체도 아니고, 유사배열 객체라고 해서 다 이터러블 객체 인것도 아니다.

위에서 예시를 든 range는 이터러블 객체이기는 하지만 인덱스도 없고 length프로티도 없으므로 유사배열객체가 아니다.

let arrayLike = { // 인덱스와 length프로퍼티가 있음 => 유사 배열
  0: "Hello",
  1: "World",
  length: 2
};

// Symbol.iterator가 없으므로 에러 발생
for (let item of arrayLike) {}

이터러블과 유사 배열은 대개 배열이 아니기 때문에 push, pop 등의 메서드를 지원하지 않는다.

Array.from

범용 메서드 Array.from은 이터러블이나 유사배열을 받아 진짜 Array를 만들어준다.
이 과정을 거친다면 이터러블이나 유사배열에 배열 메서드를 사용할 수 있다.

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};

<let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (메서드가 제대로 동작합니다.)

Array.from은 객체를 받아 이터러블이나 유사배열인지 조사하고, 넘겨받은 인수가 이터러블이나 유사배열인 경우 새로운 배열을 만들고 객체의 모든 요소를 새롭게 만든 배열에 복사한다.

Array.from에는 매핑함수를 선택적으로 넘겨줄 수 있다.

Array.from(obj[, mapFn, thisArg])

mapFn을 두번째 인수로 넣어주면 새 배열에 obj의 요소를 추가하기 전에 각 요소를 대상으로 mapFn을 적용할 수 있다.
새 배열에는 이것을 적용하고, 반환값이 추가된다.
새번쩨 인수 thisArg는 각 요소의 this를 지정할 수 있도록 해준다.

// range는 챕터 위쪽 예시에서 그대로 가져왔다고 가정합시다.

// 각 숫자를 제곱
let arr = Array.from(range, num => num * num);

alert(arr); // 1,4,9,16,25

Array.from은 str.split과는 다르게 문자열 전체가 가진 이터러블 속성을 이용해 동작한다.
따라서 for...of처럼 서로게이트 쌍에도 제대로 적용된다.

for...of사용전

let str = '𝒳😂';

// str를 분해해 글자가 담긴 배열로 만듦
let chars = Array.from(str);

alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2

for...of 사용후

let str = '𝒳😂';

let chars = []; // Array.from 내부에선 아래와 동일한 반복문이 돌아갑니다.
for (let char of str) {
  chars.push(char);
}

alert(chars);

Array.from을 사용하면 서로게이트쌍을 처리할 수 있는 slice를 직접 구현할 수도 있다(내장 순수 메서드 slice는 서로게이트 쌍을 지원하지 않기 때문이다.

function slice(str, start, end) {
  return Array.from(str).slice(start, end).join('');
}

let str = '𝒳😂𩷶';

alert( slice(str, 1, 3) ); // 😂𩷶

// 내장 순수 메서드는 서로게이트 쌍을 지원하지 않습니다.
alert( str.slice(1, 3) ); // 쓰레깃값 출력 (영역이 다른 특수 값)

출처: https://ko.javascript.info/iterable

0개의 댓글