[REAL Deep Dive into JS] 36. 디스트럭처링 할당

young_pallete·2022년 10월 11일
0

REAL JavaScript Deep Dive

목록 보기
37/46

🚦 본론

비구조화 할당으로도 잘 알려진 문법입니다.
즉 잘 구조화된 객체의 값을, 비구조화시켜서 변수에 개별적으로 각각 할당한다는 의미에요.

🙆🏻 사실 이게 끝이에요. 정말로요.

빌트인 이터러블에서의 원리

🚦 사실 이 내용은 책에 들어있지 않아요.
그렇기에 근거는 서술되어 있으나, 주관이 많이 들어가 있는, 확실하지 않은 내용입니다!
혹시나 제 의견에 오류가 있다면 댓글로 남겨주세요. 비판은 환영입니다 🥰

이터러블은 순회를 통해 값을 할당한다.

좀 더 내부를 이해해야지, 사실 이 디스트럭처링 할당의 기능을 비로소 이해 가능해요.
우리 이터러블 챕터에서 iterator라는 것을 배웠어요.

이터레이션 프로토콜을 제대로 준수한 이터러블의 이터레이터 객체는 next 메서드를 통해 순회를 합니다. 그리고 그 순회로

  • 이터레이터 리절트 객체를 반환하게 되고,
  • 우리는 해당 리절트 객체의 값을 사용할 수 있는 꼴입니다.

그렇게 하나하나~ 할당되면, 어느 순간 더이상 할당할 수 없겠죠?
그러면 실행이 끝나는 형식이에요. 더이상 할당하라는 명령이 없기 때문입니다.

따라서 스프레드와 디스트럭처링은 이터레이터 객체를 사용한다.

이제 한 번 재밌는 놀이를 해보죠!
스프레드와 디스트럭처링은, 과연 이터러블인 배열에서 어떻게 동작할까요?

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

arr[Symbol.iterator] = function () {
    let idx = 0;
    
    return {
      next() {
        console.log('test')
        return { value: arr[idx], done: idx++ === arr.length };
      }
    };
}


// 새롭게 할당되는 과정에서 `[Symbol.iterator]()`를 호출해내고, 
// 이터레이터 객체에 내장된 `next` 메서드를 강제 수행한다.
const [a, b] = arr; 

// 출력이 된다는 것은, iterator 객체의 next 메서드를 수행했다는 의미이다.
// test
// test

console.log(a, b) // 1 2

// spread는 iterable.next()를 수행한다. 따라서 test가 출력된다.
c = [...arr];
// test
// test
// test
// test
// test

console.log(c)
// [1,2,3,4]

예외 - 커스텀한 이터러블 객체에서는 어떻게 디스트럭처링이 동작하는가

요~게 가장 재미있습니다. 커스텀한 이터러블 객체는 어떻게 동작할까요?
우리 복습차원에서, 아까 했던 내용을 정리해봐요!

  1. 이터러블은 곧 이터레이터를 통해 순회가 가능하다.
  2. 배열은 기본적으로 스프레드나 디스트럭처링을 할 때 iterator 객체에 리턴된 next 메서드를 활용한다.
  3. 그렇다면 객체도 이터러블하게 만들었다면... 가능하지 않을까?

결과를 말씀드리자면, 객체의 디스트럭처링에서는 활용되지 않습니다.
왜냐! 일단 객체가 non-iterable입니다. 그럼에도 불구하고, 객체는 디스트럭처링이 동작합니다!

const obj = {a: 1, b: 2};
const {a, b} = obj
console.log(a, b) // 1, 2

// 추가로, 스프레드 문법도 객체 리터럴 내부에서는 non-iterable이어도 동작합니다.
console.log({ ...obj })

가설 정의

따라서 우리는 이 예제를 통해, 이렇게 가설을 정의할 수 있겠군요.

  1. 빌트인 이터러블은 기본적으로 스프레드와 디스트럭처링 할당에 있어 iterator 객체의 next 메서드를 호출한다.
  2. 다만 커스텀한 이터러블 Object는 이미 다른 연산을 통해 문법이 구성되어 있어, 이터레이터 객체의 next 메서드를 호출하지 않을 것이다.

증명

디스트럭처링 할당

const iterableObject = {
    a: 1,
    b: 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 };
          }
        };
    }
};

// 객체에서의 디스트럭쳐링은 직접 인덱스로 접근하여 재할당하는 방식입니다. 
// 따라서 `iterator`을 호출하지 않아요.
const {a, b} = iterableObject
console.log(a,b) // 1 2

스프레드 문법

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 };
          }
        };
    }
};

// 객체에서의 스프레드 문법 역시 따로 구현되어 있는 상태입니다. 
// 따라서 `iterator` 객체의 `next` 메서드를 호출하지 않아요.
{...iterableObject} // {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, Symbol(Symbol.iterator): ƒ}

추가 - next를 호출해내는 방법

그렇다면, 이를 호출하게 하려면 어떻게 해야 할까요?
우리는 이터레이션 프로토콜을 준수한 이터러블의 특성을 이용하면 됩니다.

이터레이션의 프로토콜을 준수하면 스프레드 문법을 통해 일련의 값들을 담아올 수 있죠?
따라서, 다음과 같이 하면 next 메서드를 호출시킬 수 있답니다 🥰

  1. 빈 배열 리터럴를 만든다.
  2. 해당 리터럴에 스프레드 문법을 통해 유사배열이자 이터러블인 iterableObject의 값 목록들을 넣는다.
  3. 그렇다면 배열은 빌트인 이터러블이고, 따라서 표준에 따라서 이터레이터 객체의 next를 호출하여 값을 생성해낸다.
// 위의 예제인 iterableObject를 사용했어요.
console.log([...iterableObject]);
// test
// test
// test
// test
// test
//  [1, 2, 3, 4, 5]

결론

결국 이를 통해, 빌트인 이터러블과, 객체는 실제로 디스트럭처링 과정에서 동작이 완전히 다르다는 것을 알게 되었네요!

정말 사실 빠르게 넘기려 했는데, 이 과정이 너무 신기하다 보니 가설을 증명하는 데까지 거의 2시간을 썼네요.

역시 뭔가를 파고드는 건 정말 재밌는 것 같아요. 다들 즐거운 공부하시길 바라며, 이상!

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

0개의 댓글