본문 바로가기
Programming/자바스크립트

[자바스크립트] iterable & iterator

by 코딩하는 랄로 2023. 11. 3.
728x90

iterable

이터러블(iterable)이란 자료를 반복할 수 있는 객체를 말한다. 반복할 수 있는다는 객체가 어떤 의미일까? 반복할 수 있는 객체, 즉 이터러블한 객체는 순회할 수 있는 객체를 의미한다. 우리가 흔히 사용하는 배열이 바로 이터러블한 객체이고 그렇기 때문에 다음과 같이 for of 문을 통한 순회도 할 수 있다.

let arr = [1,2,3]
for(const a of arr) {
    console.log (a) // 1 2 3
}

 

 

만약 위의 배열이 iterable하지 않게 된다면 어떻게 될까?

let arr = [1,2,3]
arr[Symbol.iterator] = null; // 이렇게 하면 순회가 되지 않는다

for(const a of arr) {
    console.log (a) // Uncaught TypeError: arr is not iterable
}

 

 

배열임에도 불구하고 순회할 수 없는 객체가 되어 위와 같은 에러를 띄우게 된다. 위와 같은 결과를 초래한 Symbol.iterator는 무엇인지에 대해 알아보자.

 

 

 

iterable & iterator protocol

이터러블(iterable)은 정확하게는 iterable, iterator protocol(규약, 약속)을 따르는 객체이다. 여기서 이터러블, 이터레이터 규약은 이터러블을 for-of, 전개연산자, 비구조화 등 이터러블, 이터레이터 규약을 따르는 연산자들과 함께 동작하도록 하는 약속된 규약을 의미한다. 

 

즉, 이터러블, 이터레이터 규약은 해당 규약을 따르는 연산자들은 이터러블과 함께 동작해야 한다는 규칙을 뜻하고 이 규칙을 따르는 객체가 이터러블인 것이다.

 

 

 

iterable 객체의 조건

해당 객체가 iterable한 객체가 되기 위해서는 이터레이터를 리턴하는 Symbol.iterator() 메소드를 가지고 있는 객체여야 한다. 위에서 언급한 배열 또한 해당 메소드를 상속 받아 가지고 있기 때문에 iterable 객체인 것이다.

 

 

iterator

이터레이터란 { value : 값, done : true/false } 형태의 이터레이터 객체를 리턴하는 next() 메서드를 가진 객체를 이터레이터라고 한다. 즉, 이터레이터는 next() 메소드로 순환할 수 있는 객체이다.

 

위에서 Symbol.iterator()는 iterator를 반환하는 메소드라고 하였는데 해당 메소드를 자세히 살펴보면 아래와 같이 next메소드를 가지고 있는 것을 볼 수 있다.

 

즉, { value : 값, done : true/false } 형태의 next 메서들 가지는 객체를 이터레이터이고, 이런 이터레이터 객체를 반환하는 Symbol.iterator()를 메소드를 가지는 객체를 이터러블 객체라고 하는 것이다.

 

 

 

구현 해보기

이터러블 객체와 이터레이터 객체는 처음 접하게 되면, 많이 혼동되는 개념이다. 그렇기 때문에 이터러블 객체와 이터레이터 객체를 직접 구현해보면서 두 가지 개념에 대해 이해해보자.

const iterableObj = {
    from: 1,
    to : 5
}

iterableObj[Symbol.iterator] = function() {
    return { //iterator 객체를 리턴 => next 메소드를 가지고 있어야 함
        current : this.from,
        last : this.to,

        next() { //next() 메소드는 { done : , value : } 형태의 객체를 리턴해야 함
            // done = false => 다음 next 호출
            if(this.current <= this.last) return {done:false, value:this.current++}
            // done = true => 순회 종료
            else return {done:true}
        }
    }
}

 

위의 코드에서 Symbol.iterator가 리턴하는 next() 메소드를 가지는 객체가 이터레이터 객체이고 이를 반환하는 Symbol.iterator 메소드를 가지고 있는 iterableObj가 이터러블 객체인 것이다.

 

위의 예제에서 iterableObj를 for-of 를 통해 순회하게 되면 아래와 같이 잘 동작하는 것을 볼 수 있다.

const iterableObj = {
    from: 1,
    to : 5
}

for(let x of iterableObj) { // {} 는 for-of 사용 X
    console.log(x) //Uncaught TypeError : iterableObj is not iterable
}

iterableObj[Symbol.iterator] = function() {
    return { //iterator 객체를 리턴 => next 메소드를 가지고 있어야 함
        current : this.from,
        last : this.to,

        next() { //next() 메소드는 { done : , value : } 형태의 객체를 리턴해야 함
            // done = false => 다음 next 호출
            if(this.current <= this.last) return {done:false, value:this.current++}
            // done = true => 순회 종료
            else return {done:true}
        }
    }
}

// 정상 작동!!
// 하지만 기존의 객체가 가지고 있는 프로퍼티와는 다르다?
for(let x of iterableObj) { 
    console.log(x) //1 2 3 4 5
}

 

하지만, 출력 결과를 보면 객체의 기존의 프로퍼티인 from, to를 출력하는 것이 아닌 1, 2, 3, 4, 5를 출력하는 것을 볼 수 있다. 이를 이해하기 위해서는 for-of문을 통해 iterable 객체를 순회하는 과정에 대해서 이해를 해야 한다.

 

위의 예제에서 for-of문을 호출하게 되면 다음과 같은 과정을 거친다.

  • for-of 호출시, 해당 이터러블 객체의 Symbol.iterator()가 호출됨
  • Symbol.iterator()는 이터레이터 객체를 반환
  • 이후, for-of는 반환된 이터레이터 객체만을 대상으로 동작
  • for-of가 반환된 이터레이터 객체의 next를 호출
  • next에 의해 반환된 객체의 value값을 for-of문의 let x 변수에게 할당!!
  • next에 의해 반환된 객체의 done 프로퍼티가 true가 나올 때까지 next 메소드를 호출하면서 순회

 

위의 코드는 이터러블 객체안에 한꺼번에 구현하는 것도 가능하다.

const iterableObj = {
    from: 1,
    to : 5
    
    [Symbol.iterator]() {
        this.current = this.from;
        this.last = this.to;
        return this;
    },
    
    next() { 
        if(this.current <= this.last) return {done:false, value:this.current++}
        else return {done:true}
    }
}

for(let x of iterableObj) {
    console.log(x); // 1 2 3 4 5
}

 

 

 

 

Array.from

위에서 구현한 iterableObj는 객체이다. 순회할 수 있을 뿐이지 해당 객체가 배열 취급을 받는 것은 아니기 때문에 이터러블 규약을 따르는 연산자(for-of, 전개 연산자 등)은 사용할 수 있지만, 배열은 아니기 때문에 배열의 메소드는 사용할 수 가 없다. 

 

하지마 Array의 정적 메소드인 from을 사용할 경우, 해당 객체를 배열로 반환해주기 때문에 배열의 메소드를 사용할 수 가 있다. Array.from은 인수로 이터러블 객체 또는 유사 배열만을 받는다. 

const iterableObj = {
    from: 1,
    to : 5
    
    [Symbol.iterator]() {
        this.current = this.from;
        this.last = this.to;
        return this;
    },
    
    next() { 
        if(this.current <= this.last) return {done:false, value:this.current++}
        else return {done:true}
    }
}

// Array.from
// arrayLike 객체를 배열로 변환 해줌
// arrayLike 객체 => 유사 배열 객체, 이터러블 객체
Array.from(iterableObj).forEach(el => console.log(el)); //1 2 3 4 5

 

 

 

문자열

문자열 또한 이터러블 객체이기 때문에 for-of 문 등 이터러블 규약을 따르는 연산자를 이용할 수 있다.

for (let char of "javascript") {
  console.log( char ); // j a v a s c r i p t
}
728x90