Frontend/JS.info 정리

자료구조와 자료형 - iterable 객체

Creative_Lee 2022. 2. 12. 15:33

iterable 객체

 

iterable 객체는 배열을 일반화한 객체입니다!

iterable 개념을 사용하면 어떤 객체에든 for..of문을 사용할 수 있습니다.

배열은 대표적인 이터러블이고, 문자열 역시 이터러블의 예입니다!

배열 외에도 다수의 내장 객체가 이터러블입니다.

 


 

 

Symbol.iterator

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

위와 같은 객체가 있습니다. 1부터 5까지의 간격을 나타내고 있죠.

위 객체에 for... of 문을 사용하면 다음과 같은 에러 메세지가 나타납니다.

위 range객체를 iterable로 만들려면 ( for..of 사용 가능한 상태 ) 
객체에 Symbol.iterator 라는 메서드를 추가해야 합니다.

메서드 추가 후 for ...of 문을 사용하면 다음과 같이 동작합니다.

 

1. iterable 객체( Symbol.iterator 메소드를 추가한 객체)에 for.. of 문을 사용했습니다!

2. 바로 Symbol.iterator 메서드를 호출합니다. 

3. Symbol.iterator 메서드는 항상 iterator 객체를 리턴합니다.

4. 이후 for.. of문은 리턴된 iterator 객체를 대상으로 동작합니다.

5. iterator 객체의 next() 메소드를 호출하여 반환된 리턴값을 for.. of문의 반복을 위한 다음 값으로 사용합니다.

6. next() 의 리턴값은 항상 { done : Boolean , value : any } 와 같은 형태여야 합니다.

7. next() 의 리턴값이 { done : false } 라면 반복을 계속하고 value에 다음 값을 저장합니다.

8. next() 의 리턴값이 { done :  true }가 될 때까지 반복합니다.

 

 

이제 iterable 객체로 만들어 보겠습니다.

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

  [Symbol.iterator](){    	// 메소드 추가!
    return {			// 메소드는 iterator 객체를 리턴합니다!
      current : this.from,	// 이후 iterator 객체를 대상으로 for of문이 작동하기 때문에
      last : this.to,		// 값을 추가해 줍니다.
      
      next(){			// 반복을 위한 다음 값이 필요할 때 next() 메소드를 실행합니다.
        if (this.current <= this.last){
          return { done : false , value : this.current++} // 반복을 계속하고 value에 다음 값을 저장합니다. 
        }
        else{
          return { done : true }	// 반복을 종료합니다.
        }        
      }
    }
  },
};

range 객체와 range[ Symbol.iterator ]( ) 를 호출해서 만든 iterator 객체를 분리했습니다.

위와 같이 코드를 짜면 iterator 객체와 반복 대상 객체를 분리할 수 있습니다!

 

 

 

또는 다음 코드와 같이 iterator 객체와 반복 대상 객체를 합쳐 

반복 대상 객체 자체를 iterator로 만들면 코드가 더 간단해집니다.

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

  [Symbol.iterator](){
    this.current = this.from
    return this
  },

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

위 코드에서 range[ Symbol.iterator ]( ) 메소드가 객체 range 자체를 리턴합니다.

리턴된 객체에는 당연히 필수 메소드 next( ) 가 포함되어 있고 this.current 에는 반복 진행 횟수도 저장되이 있습니다.

 

위와 같은 코드의 단점은 for..of 반복문을 하나에 객체에 동시에 사용할 수 없다는 점입니다!

iterator가 객체 자신. 단 하나 뿐이여서 두 반복문이 반복 상태를 공유하기 때문입니다.

하지만 동시에 2개의 for..of문을 사용하는 것은 흔한 케이스가 아니라고 하니 걱정하지 맙시다!

 


 

문자열도 iterable 입니다!

문자열은 가장 광범위하게 쓰이는 내장 iterable입니다.

for(let char of "hello"){
  console.log(char)
}
// h, e, l, l, o 가 차례대로 출력됩니다!

 

 


 

iterator 명시적으로 호출하기

for.. of문을 사용했을 때 와 같이 직접 호출을 통해 문자열을 순회해 보겠습니다!

let str = "안녕하세요"

let iterator = str[Symbol.iterator](); // str의 iterator 메소드 실행

while(true){
  let result = iterator.next(); // iterator 객체의 next 메소드를 실행한 값 == 객체 할당
  if(result.done) break; // next메소드의 리턴값인 객체의 done == true 면 반복종료
  console.log(result.value) // next메소드의 리턴값인 객체의 value 출력
}

// 안, 녕, 하, 세, 요 출력

위와 같이 iterator를 명시적으로 호출하는 경우는 거의 없지만,

반복 시작 후 잠시 멈추고 다른 작업을 하다가 반복을 재개하는 것 처럼

반복 과정을 여러 개로 나누는 것이 가능합니다.

 

 


 

iterable 과 유사 배열

 

iterable 객체  :  Symbol.iterator 메소드가 구현된 객체

유사 배열 객체 : index 와 length 프로퍼티가 있어서 배열처럼 보이는 객체

 

iterable 객체이면서 유사 배열 객체 일 수 있습니다.

iterable 객체라고 유사 배열 객체인 것은 아닙니다.

유사 배열 객체라고 iterable 객체인 것은 아닙니다.

 

iterable과 유사 배열 객체는 둘다 배열이 아니기 때문에 pop, push와 같은 메서드를 사용 할 수 없습니다.

위 둘을 배열처럼 다루고 싶을 때 상당히 불편하죠!

어떻게 하면 배열 메소드를 사용 할 수 있을까요 ?

 

 

Array.from( )

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

 

iterable객체나 유사 배열 객체를 인자로 받아 진짜 Array로 만들어 줍니다.

이후 배열 메서드를 사용할 수 있습니다!

 

메소드 사용시 객체를 받아 평가 후 새로운 배열을 만들어 객체의 모든 요소를 복사합니다!

let 유사배열 = { 
  0 : '안녕하신가',
  1 : '안녕하세요',
  length : 2
}

let 이젠진짜배열 = Array.from(유사배열)
console.log(이젠진짜배열.pop()) // 안녕하세요
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator](){
    this.current = this.from
    return this
  },

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

let 이젠진짜배열 = Array.from(range)
console.log(이젠진짜배열) // [1, 2, 3, 4, 5]

 

 

Array.from() 메소드 사용 시 2번째 인자로 함수를 전달 할 수 있는데,

새로운 배열에 객체의 모든 요소를 복사하기 전에

각 요소를 대상으로 함수를 적용해 리턴된 값을 새로운 배열에 추가합니다.

map() 메소드를 쓰는거라고 생각하면 되겠습니다.

let 이젠진짜배열 = Array.from(range, ele => ele * 2)
console.log(이젠진짜배열) // [2, 4, 6, 8, 10]

 


 

 

요약 정리

 

  • for..of문을 사용할 수 있는 객체를 iterable 이라고 한다.
    iterable에는 [Symbol.iterator]( ) 메소드가 구현되어 있어야 한다.

  • for..of문을 사용하면 [Symbol.iterator]( ) 메소드를 호출하고
    [Symbol.iterator]( ) 는 iterator 객체를 리턴한다.

  • iterator 객체는 이어지는 반복 과정을 처리한다.
    iterator 객체에는 객체를 리턴하는 next( ) 메소드가 구현되어 있어야 하고
    for..of 반복문 진행중 다음 값이 필요하면 next( ) 호출된다.
  • next( )가 리턴하는 객체는 { done : Boolean , value : any } 형태이다.
    done 값에 따라 for..of 반복문이 반복, 정지 한다.
    value에는 각 단계 진행마다 반복을 위한 다음 값이 저장된다.

  • Symbol.iterator 는 for..of에 의해 자동으로 호출되지만 명시적 호출이 가능하다.

  • 문자열, 배열 같은 내장 iterable 에도 Symbol.iterator 메소드가 구현되어 있다.
  • index와 length 프로퍼티가 있는 객체를 유사 배열이라고 부른다.
    유사 배열에서는 배열 내장메서드를 사용할 수 없다.

  • Array.from( ) 메소드를 사용해 iterable, 유사 배열을 진짜 Array로 만들 수 있다.
    이후 배열 메서드 사용이 가능하다.

 

 

 

기본이 중요하다!