[REAL Deep Dive into JS] 34. 이터러블

young_pallete·2022년 10월 11일
0

REAL JavaScript Deep Dive

목록 보기
35/46

🚦 본론

배경

항상 모든 것에는 "왜 탄생되었는지"를 이해하는 것이 수반되어야 합니다.
우리, 그냥 단순히 받아들이지 말고 생각을 해봅시다.

💡 왜 이터러블이 탄생되었을까요?

우리, 유사 배열 객체라는 것을 기억하나요?
length를 가지고 있으면서, 마치 배열처럼 구성되어 있는 객체를 의미했죠!

그런데, 이 친구들 문제가 있어요 😖
바로 순회가 불가능하다는 점이었습니다.

하지만 우리의 개발자분들! 이를 순회 가능하게 만들었어요.
이를 forEach 등으로 메서드를 사용하여 순회가 가능하게 만들었는데요.

😱 문제는, 이러한 순회가 표준화되어 있지 않았다는 점이었어요...

이러한 현상이 반복되다 보면, 결과적으로 앞으로의 자료구조나, 다른 라이브러리를 참고할 때 굉장히 많은 혼란이 발생하겠죠?

따라서 이터러블은 이러한 맥락에서 탄생되었습니다.

💡 "우리, 이런 것들 잘 지켜준 객체들은 적어도 일관성 있게 순회 가능하게 해주자!"

결과적으로, 순회에서는 좀 더 클린한 자바스크립트 세상이 탄생한 순간이었겠군요. 🎉

이터레이션 프로토콜

프로토콜은 약속이죠. 따라서 이터레이션은 자료구조에 있어 순회에 대한 일련의 규칙입니다.
이는 물론 ECMAScript 사양을 근거하여 정의되어 있는데요.

우리는 크게 2가지를 기억하면 됩니다.

  • 이터러블 프로토콜
  • 이터레이터 프로토콜

이 친구들을 합해서 이터레이션 프로토콜을 구성하기 때문입니다.
이름이 비슷하여 어려워 보이지만, 막상 이해하면 그렇게 어렵지 않습니다.

이터러블 프로토콜

이터러블은 무슨 뜻일까요?

iterate는 반복하다라는 의미니까... 음, 반복 가능한의 의미려나요? 🙇🏻‍♂️

맞습니다. 결국 반복문이 가능하다는 의미겠죠?

💡 그렇다면, 이터러블 프로토콜은, 이를 지켜야만 반복문 수행이 가능하다는 의미를 갖게 되겠군요!

따라서 이를 너무 syntactic하게 외우는 것은 오히려 혼동스럽습니다.
오히려, context로 이해하는 게 더 편한데요.
이터러블 프로토콜은 결국 반복문(for ... of ...)의 조건을 이야기하는 거라 생각하면 됩니다. 그리고 이를 준수하면 우리는 그 객체를 이터러블이라고 얘기를 합니다.

그렇다면, 그 조건은 뭘까요?
핵심은 바로, 이전 챕터에서 설명했던 'Well-known Symbol'인 Symbol.iterator입니다.

조건 1. Symbol.iterator 프로퍼티 키로 정의한 메서드가 있는가.

해당 프로퍼티 키를 가진 메서드가 있다면, 우리는 그 객체를 이터러블이라 정의할 수 있습니다.

const iterableObject = {
    0: 1,
    1: 2,
    2: 3,
    3: 4,
    4: 5,
	[Symbol.iterator]() {
    	let cur = 0;
	    const max = Object.keys(iterableObject).length;
        
        return {
          next() {
            console.log('test')
            return { value: iterableObject[cur], done: cur++ === max };
          }
        };
    }
};

// 이제는 객체 순회를 통해 값에 접근할 수 있군요! 😮
for (let i of iterableObject) {
	console.log(i)
}

// 1
// 2
// 3
// 4
// 5

조건 2. Symbol.iterator 메서드를 상속 받았는가.

대표적으로 배열이 있겠죠.
배열은 Array.prototype을 상속받고, Array.prototype에는 [Symbol.iterator]로 정의된 메서드가 내장되어 있습니다. 따라서 배열은 이터러블입니다.

const arr = [1,2,3,4];

// arr은 Array를 상속받았으므로 Symbol.iterator 프로퍼티가 존재한다.
console.log(Symbol.iterator in arr); // true;

// Array의 [[Prototype]]에는 Symbol.iterator 프로퍼티 메서드가 존재한다.
console.log(Symbol.iterator in arr.__proto__); // true;

💡 책으로 보니, 이터러블 객체를 판단할 수 있는 유틸 함수를 만드셨군요!

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

이터레이터 프로토콜

그럼 이제, 이터레이터 프로토콜을 살펴볼 건데요. 이터레이터는 Symbol.iterator에 들어간 메서드에 관한 프로토콜이라고 이해하면 쉬울 것 같아요! 🥰

이 친구는 아까의 예시를 갖고와서 설명해볼까 해요.

이터러블의 조건

const iterableObject = {
    0: 1,
    1: 2,
    2: 3,
    3: 4,
    4: 5,
	[Symbol.iterator]() {
    	let cur = 0;
	    const max = Object.keys(iterableObject).length;
        
        // 이 부분이 이터레이터 프로토콜을 따른 객체입니다!
        return {
          next() {
            console.log('test')
            return { value: iterableObject[cur], done: cur++ === max };
          }
        };
    }
};

해당 리턴 값의 객체가 보이시나요?
이 객체는 next라는 메서드를 갖고 있는데요!
next의 리턴값에는 valuedone이 있네요.
참고로, 이 next 메서드의 리턴값을 우리는 이터레이터 리절트 객체라 부른답니다.

이는 다음을 의미하고 있어요.

  • next(): 다음에 반환되는 것이 무엇인지를 정의해요.
  • value: 현재의 값을 의미해요.
  • done 계속해서 순회할 때, 언제 순회를 끝낼지를 판단하기 위한 조건식을 정의해요.

네, 이터레이터의 조건은 이 3가지를 만족하면 된답니다.
이렇게 생각하니 어렵지 않죠? 😆

사용 예시

const iterable = '1234'; // String 래퍼 객체도 이터러블입니다.
const iterator = iterable[Symbol.iterator]();

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: '4', done: false}
console.log(iterator.next()) // {value: undefined, done: true}

중간 정리

아무래도 이름이 워~낙 헷갈려서 한 번 더 정리해보는 게 우리의 기억에 도움이 될 것 같네요.
핵심은 다음과 같아요.

  1. 이터레이션 프로토콜은 크게 이터러블이터레이터에 관한 프로토콜로 구성된다.
  2. 이터러블이란 결국 Symbol.iterator 프로퍼티 키를 갖고 있느냐로 판단된다.
  3. 이터레이터 프로토콜는 이터러블이 정상적으로 순회 가능하도록, Symbol.iterator 메서드에 mapping된 함수가 잘 정의되어 있는지에 대한 규약이다.
  4. 이터레이터 프로토콜은 결국, next 메서드와, next 메서드가 이터레이터 리절트 객체(valuedone)을 잘 구성했는지로 준수 여부를 판단한다.

자, 이제 이해가 되었나요?

그러나 우리의 여정은 아직 끝나지 않았으니, 좀 더 살펴보도록 하죠! 🔥

빌트인 이터러블

쉽게 말하자면, 내장 빌트인 객체 중에 어떤 것들이 이터러블한지에 대한 설명이 책에 들어있군요!

  • Array
  • String
  • Map
  • Set
  • TypedArray (이 친구는 어려울 수 있는데, 타입과 버퍼에 따른 값을 넣을 수 있도록 하는 배열 인스턴스를 만드는 객체의 프로토타입 객체에요.)
  • arguments
  • DOM Collection

for ... of ...

결국 이터러블이 이터러블하게 동작할 수 있도록 하는 핵심인데요!
이터러블 순회에 관한 문법입니다.

vs for ... in ...

가끔 이와 헷갈리시는 분들을 봤었어요.
핵심은, 결국 이터러블하냐가 맞습니다.

다만, for ... in ...은 객체의 프로토타입 체인에 존재하는 프로퍼티들 중, [[Enumerable]]: true인 친구들과 Symbol 프로퍼티가 아닌 프로퍼티들의 키를 열거하는 친구에요.

그러나 우리의 for ... of ...는 뭐라고 했죠? 이터러블 객체의 순회를 위한 친구라 했죠! 따라서 객체는 이터러블하지 않으면 작동하지 않습니다.

또한 Symbol.iterator에서의 리절트 객체의 value는 프로퍼티 값이 할당되어 있고, done으로 조건을 판단해요. 이 과정에서 좀 더 표준화된 결과값을 출력하게 되어 있는데요.

따라서 for ... of ...는 유효한 프로퍼티 값을 순회하게 된답니다 😉

a = [1,2,3,4]
a.foo = () => {};

for (let i of a) { console.log(i) } // 1 2 3 4
for (let i in a) { console.log(i) } // 0 1 2 3 foo

++ 추가적으로 제가 알기로는, for ... of ...는 내부 연산에 있어서 결국 다음 value를 기억할 수밖에 없기도 하고, 모든 프로토타입 체인을 추적하지 않습니다. 따라서 성능이 더욱 우수합니다.

이터레이터를 활용한 지연 평가

책에 있는 예제를 통해 지연 평가를 살펴 보도록 하죠!

const fibonacciFunc = function() {
    // 클로저로 구현해보도록 하죠!
	let [pre, cur] = [0, 1];
    
    return {
      [Symbol.iterator]() {
      	return {
          next() {
            // 순회하면서, pre와 cur을 업데이트합니다.
            [pre, cur] = [cur, pre + cur];
            
            
            // 우리는 끝나지 않는 함수를 만들어볼까 해요.
            // 기본적으로 done의 값은 설정되어 있지 않다면 not truthy합니다.
            return {
              value: cur, 
            }
          }
        }
      }
    }
}

var [a, b, c, d, e] = fibonacciFunc();
console.log(a,b,c,d,e) // 1 2 3 5 8

이처럼, 이터러블의 iterator를 잘 활용한다면 데이터를 미리 할당하지 않고, 필요할 때에 평가하고 생성해내는 지연 평가가 가능해집니다.

이건 최적화를 위해 사용합니다.
특히, 함수형 프로그래밍에서는 자주 봤던 것 같아요!
보통 함수형 프로그래밍이 느리다고는 하지만, 결국 이러한 지연 평가 등을 통해 메모리를 최적화시키는 기법도 존재해서 정말 잘 사용하면, 성능이 빠른 구현도 가능하다고 합니다.

🎉 마치며

후우... 이전에 이터러블에 관한 글을 노션에 썼던 기억이 나요.
다시 보고 나니, 그때는 정말 잘 이해하지 못하고 썼다는 느낌이 드네요. 지금에서야 뭔가 제대로 이해하는 느낌이에요.

현재 포트폴리오 웹사이트를 만들고 있는데요, 이것이 끝나면 본격적으로 함수형 프로그래밍을 연습해보려 합니다.

이유는 간단합니다. 재밌어 보이기 때문이죠 😉

굳이 공부에 너무 하나하나의 납득할 만한 배경을 갖고 있지는 않아도 되고,
그렇기 때문에 아직 일부러 지원서도 넣지 않고 있어요.
(실제로 일하면서 원하는 공부를 하기에는, 일을 배우는 입장에서 어렵더라구요)

여튼, 공부하는 이 순간이 굉장히 즐겁네요. 다들 즐거운 공부하시길 바라며, 이상! 🌈

참고자료

MDN - TypedArray

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

0개의 댓글