본문 바로가기

JavaScript

20210714 JavaSciprt DeepDive 12 : Symbol, 이터러블(Iterable), 스프레드 문법, 디스트럭처링 할당(구조 분해 할당)

JavaScript Deep Dive 12



📄 용어 및 중요사항 정리


Symbol


심벌


  • 변경 불가능한 원시 타입의 값
  • 다른 값과 중복되지 않는 유일무이한 값
  • 이름 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용함

심벌 값의 생성


  • Symbol 함수 :
    • Symbol 함수를 호출하여 심벌을 생성함 (다른 원시값의 경우 리터럴 표기법을 통해 값 생성)
    • new 연산자와 함께 호출하지 않음 (인스턴스가 아니라 원시값을 생성하는 것임)
    • 생성된 심벌 값은 외부로 노출되지 않아 확인 불가함
    • Symbol(description) : Symbol 함수의 인수로는 생성된 심벌값에 대한 설명인 문자열을 전달 가능함
      • 설명 문자열이 같더라도 언제나 Symbol은 변경 불가한 유일무이한 값을 줌으로서 다름
    • 생성된 심벌 값에 객체처럼 접근하면 다른 원시 값과 같이 래퍼 객체를 생성
    • 심벌 값은 암묵적으로 문자열이나 숫자 타입으로 변환되지 않음
      • 불리언 타입으로는 암묵적 타입 변환이 이루어짐 -> 심벌 값의 존재 확인 가능

  • Symbol.for(stringKey) : 인수로 전달 받은 문자열을 키로 사용하여 키와 심벌 값의 쌍들이 저장되어 있는 전역 심벌 레지스트리에서 이미 저장된 심벌 값을 검색하거나, 값을 생성함
    • 전달된 인수(stringKey)가 존재하는 경우, -> 해당 인수에 맞는 심벌 값을 반환
    • 전달된 인수(stringKey)가 존재하지 않는 경우, -> 해당 인수에 맞는 심벌 값을 생성하여 저장하고 반환
    • 모든 곳에서 중복되지 않는 상수 역할의 심벌 값을 관리하여 공유 할 수 있음

  • Symbol.keyFor(symbolValue) : Symbol for로 생성된 심벌값을 인수로 전달하면, 전역 심벌 레지스트리에서 해당 심벌값의 생성할 당시의 key를 찾아 반환해줌

  • 변경/중복될 가능성이 있는 상수 대신 중복 가능성 없는 심벌을 사용할 수 있음

  • enum : 명명된 숫자 상수의 집합으로 열거형(enumerated type)이라고 함
    • JS는 enum을 지원하지 않음, TS는 enum을 지원함

  • 심벌인 프로퍼티 키 :
    • 객체의 프로퍼티 키를 심벌로 지정가능하고, 동적으로 생성 가능함
    • .을 통한 프로퍼티 값 접근은 불가함
    • [] 대괄호를 사용하여 안에 심벌 값을 넣어 프로퍼티 값에 접근 가능함
    • 기존의 프로퍼티 키, 미래에 사용할 프로퍼티 키와 절대로 충돌이 없음
const obj = {
  [Symbol.for("mySymbol")]: 1,
};

obj[Symbol.for("mySymbol")]; // 1

  • 심벌인 프로퍼티 은닉
    • 심벌 값을 프로퍼티 키로 사용한 프로퍼티는 for...in, Object.keys, Object.getOwnPropertyNames 와 같은 일반적인 메서드로는 찾을 수 없음 -> 외부로 부터 은닉이 됨
    • Object.getOwnPropertySymbols 메서드 사용시 해당 심벌 프로퍼티 키를 찾을 수 있음(찾아서 배열로 반환 함)
    • 해당 프로퍼티 키를 찾아 가져와서, 객체에 대괄호[ ]로 접근하여 값을 가져올 수 있음

  • 심벌과 표준 빌트인 객체 확장
    • 표준 빌트인 객체에 사용자 정의 메서드를 직접 추가하면, 나중에 도입되는 메서드와 충돌이 날수 있음
    • 심벌을 통해서 메서드 이름으로 사용하여 추가하면 충돌을 해결 할 수 있음
Array.prototype[Symbol.for("sum")] = function () {
  return this.reduce((acc, cur) => acc + cur, 0);
}[(1, 2, 3, 4)][Symbol.for("sum")](); // 10

  • Well-known Symbol : JS가 기본 제공하는 빌트인 심벌 값
    • 순회 가능한 빌트인 이터러블은 Symbol.iterator를 키로 갖는 메서드를 가짐
    • Symbol.iterator 메서드 호출시 이터레이터를 반환함



이터러블 (Iterable)


이터레이션 프로토콜


  • 이터레이션 프로토콜 : 순회 가능한 데이터 컬렉션(자료구조)를 만들기 위해 약속한 규칙(이터러블 프로토콜 + 이터레이터 프로토콜)
    • 순회 가능한 데이터 컬렉션 : 배열, 문자열, 유사 배열 객체, DOM 컬렉션 등..
      • ES6 이전 for문, for...in문, forEach등으로 다양한 방법으로 순회 방식
    • 이터레이션 프로토콜을 준수하는 이터러블 통일
      • for ... of문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용 가능
    • 데이터 소비자(사용방식)와 데이터 공급자(자료형)를 연결하는 인터페이스 역할 - 순회하는 다양한 자료형에 따라 사용 메서드가 많아지면, 사용에 혼선이 있고 비효율적임 - -> 순회하는 다양한 자료형에 규칙을 두어 규칙을 만족하면, 정해진 사용방식을 통해 다양한 자료형을 사용할 수 있게함
  • 이터러블 프로토콜 :
    • Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현시, 프로토타입체인을 통해 상속 받은 Symbol.iterator 메서드 호출시 이터레이터 프로토콜을 준수한 이터레이터를 반환하는 이터러블을 사용해야하는 규약

  • 이터레이터 프로토콜 :
    • 이터러블의 Symbol.iterator 메서드 호출시 (next 메서드를 소유하고, next 메서드 호출시 이터러블을 순회하며 이터레이터 result 객체를 반환하는) 이터레이터를 사용해야 하는 규약

  • 이터러블 : Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나, 프로토타입체인을 통해 상속 받아 Symbol.ierator 참조, 호출시 이터레이터를 반환하는 객체
    • 배열, 문자열, Map, Set 등은 이터러블
    • for...of 문으로 순회 가능, 스프레드 문법, 배열 디스트럭처링 할당(배열 구조분해 할당)의 대상으로 사용 가능
      • 스프레드 문법은 일반 객체도 사용 가능

  • 이터레이터 : next 메서드를 소유하여 next 메서드 호출시 이터러블을 순회하며 매 순회 마다 이터레이터 result 객체를 반환하는 함수객체
    • 이터러블의 요소를 탐색하기 위한 포인터 역할

  • 이터레이터 result 객체 :
    • {value: any, done: boolean}
    • value 프로퍼티 : 현재 순회 중인 이터러블의 값
    • done 프로퍼티 : 이터러블 전체 순회 완료 여부



빌트인 이터러블


  • 이터레이션 프로토콜을 준수한 객체인 JS 자체(빌트인) 이터러블

  • 빌트인 이터러블인 표준 빌트인 객체

빌트인 이터러블 Symbol.iterator 메서드
Array Array.prototype[Symbol.iterator]
String String.prototype[Symbol.iterator]
Map Map.prototype[Symbol.iterator]
Set Set.prototype[Symbol.iterator]
TypeArray TypeArray.prototype[Symbol.iterator]
arguments arguments[Symbol.iterator]
DOM 컬렉션 NodeList.prototype[Symbol.iterator]
HTMLCollection.prototype[Symbol.iterator]

for ... 문


  • for (변수선언문 of 이터러블) {}
    • 이터러블 순회하면서 이터러블의 요소를 변수에 할당
      • 내부적으로 이터레이터의 next 메서드 호출시 반환되는 result 객체의 value 프로퍼티 값을 변수에 할당하여 순회함
      • result 객체의 done 프로퍼티 값이 true가 되면 이터러블의 순회 중단함

  • for (변수선언문 in 객체) {}
    • 객체의 프로토타입 체인상에 존재하는 모든 프로토타입 프로퍼티 중에서 [[Enumerable]] 값이 true인 프로퍼티를 순회하며 열거
    • 프로퍼티 키 symbol인 경우는 열거 제외

이터러블 vs 유사 배열 객체


  • 유사 배열 객체

    • 인덱스로 프로퍼티 값에 접근, length 프로퍼티를 갖는 객체
    • for문 순회 O / for ...of 순회 X
    • Symbol.iterator 없음
    • 이터러블X, 일반 객체O
    • Array.from 메서드로 배열로 반환하여 이터러블로 만들수 있음
  • 이터러블인 유사 배열 객체

    • arguments, NodeList, HTMLCollection

사용자 정의 이터러블


  • 사용자 정의 이터러블 : 이터레이션 프로토콜을 준수하도록 설정한 일반 객체
    • Symbol.iterator 메서드 구현
      • 반환되는 이터레이터 구현
        • 반환되는 result 객체 구현
// 사용자 정의 이터러블
// 피보나치 수열의 10 이하의 값만 나타내는 이터러블
const fibonacci = {
  // Symbol.iterator 메서드 구현
  [Symbol.iterator]() {
    let [pre, cur] = [0, 1];
    const max = 10;

    // 이터레이터 반환
    return {
      // next 메서드 구현
      next() {
        [pre, cur] = [cur, pre + cur];
        // result 객체
        return { value: cur, done: cur >= max };
      },
    };
  },
};

// for ... of
for (const num of fibonacci) {
  console.log(num); // 1, 2, 3, 5, 8
}
// 스프레드 문법
const arr = [...fibonacci];
console.log(arr); // 1, 2, 3, 5, 8

// 배열 디스턱처링 할당
const [first, second, ...rest] = fibonacci;
console.log(first, second, rest); // 1 2 [3, 5, 8]

  • 이터러블을 생성하는 함수
    • 필요한 인수를 받아 변하는 이터러블을 생성하는 함수 구현
// 이터러블 반환 함수
const fibonacciFunc = function (max) {
  let [pre, cur] = [0, 1];
  // 이터러블 반환
  return {
    // Symbol.iterator 메서드 구현
    [Symbol.iterator]() {
      // 이터레이터 구현
      return {
        // next 메서드 구현
        next() {
          [pre, cur] = [cur, pre + cur];
          // result 객체 반환
          return { value: cur, done: cur >= max };
        },
      };
    },
  };
};

// for ... of
for (const num of fibonacciFunc(10)) {
  console.log(num); // 1 2 3 5 8
}

  • 이터러블이면서 이터레이터인 객체를 생성하는 함수
    • next 메서드를 활용하고자 하면 이터레이터를 받아와야 함, Symbol.iterator 메서드를 호출해서 이터레이터를 받아와야함
    • 그럴 필요 없이 이터러블에서 바로 next 메서드를 만들어 사용할수 있게 함
const fibonacciFunc = function (max) {
  let [pre, cur] = [0, 1];

  // 이터러블
  return {
    [Symbol.iterator]() {
      return this; // this는 이터러블
    },
    next() {
      [pre, cur] = [cur, pre + cur];
      return { value: cur, done: cur >= max };
    },
  };
};
let iter = fibonacciFunc(10);
for (const num of iter) {
  console.log(num); // 1 2 3 5 8
}
iter = fibonacciFunc(10);
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: 5, done: false}
console.log(iter.next()); // {value: 8, done: true}

  • 무한 이터러블
    • result 객체의 done 프로퍼티를 명시하지 않은 경우
    • 무한으로 값을 생성하여 가져올 수 있음(필요한 만큼만 for...of, 배열 디스트럭처링 할당에서 결정하여 가져옴)

  • 지연 평가 : 데이터가 필요한 시점 이전까지는 미리 데이터를 생성하지 않고 필요한 시점이 되면 그때 데이터를 생성하는 기법
    • 평가 결과가 필요할때(for...of, 배열 디스트럭처링 할당) 까지 평가를 늦춤
    • for...of문의 경우 이터레이터 next 메서드 호출할 때 데이터가 생성됨



스프레드 문법 (spread syntax, 전개 문법)


  • 하나로 뭉쳐 있는 여러 값들의 집합을 펼쳐서 개별적인 값들의 목록으로 만듦
  • 사용 가능 대상
    • 이터러블에 한정
    • Array, String, Map, Set, DOM 컬렉션(NodeList, HTMLCollection), arguments
  • 스프레드 문법의 결과는 값이 아니라 값의 목록 임
  • 스프레드 문법의 결과는 변수에 바로 할당 할수 없음
  • 값 목록을 사용하는 문맥
    • 함수 호출문의 인수 목록
    • 배열 리터럴의 요소 목록
    • 객체 리터럴의 프로퍼티 목록

함수 호출문의 인수 목록에서 사용


  • 함수 호출문에서 인수를 전달할 때 사용하는 인수 목록으로 활용 가능함
    • 스프레드 문법 전에는 apply를 통해서 인수 배열을 제공하여 인수를 전달 했음
  • rest 파라미터와 반대의 개념
    • rest 파라미터 : 인수 목록 -> 배열
    • spread 문법 : 배열 -> 인수 목록
const arr = [1, 2, 3, 4, 5];

const applyMax = Math.max.apply(null, arr); // 5
const spreadMax = Math.max(...arr); // 5


배열 리터럴 내부에서 사용


  • 2개의 배열을 하나로 합치고 싶은 경우

    • 과거 : Array.prototype.concat(arr)
    • spread 문법 방식 : [...Array1, ...Array2]
  • Array1을 Array2에 특정한 위치에 넣고 싶은 경우

    • 과거 : splice + apply + concat
    • spread 방식 : splice에 3번 째 인자 부터 풀어서 넣음
  • 배열 복사를 하고 싶은 경우

    • 과거 : slice함수 호출
    • spread 방식 : [] 빈 배열에 넣어 풀어사용
  • 이터러블, 유사배열 객체를 배열로 변환하고 싶은 경우

    • 과거 : slice + call or Array.from
    • spread 방식 : [] 빈 배열에 넣어 풀어사용
      • 단, 이터러블이 아닌 유사배열 객체는 불가
      • from은 이터러블이 아닌 유사배열 객체도 가능


객체 리터럴 내부에서 사용


  • 스프레드 프로퍼티를 사용하면 객체 리터럴의 목록에서도 스프레드 문법 사용 가능
    • 일반 객체 대상으로도 스프레드 문법 사용 허용
    • 복사 또는 병합으로 많이 사용
    • 과거: Object.assign 메서드를 통해서 병합, 변경하였음
    • spread 방식 : 빈 객체{}에 풀어서 넣어주면 됨



디스트럭처링 할당 (구조 분해 할당)


  • 배열과 같은 이터러블 또는 객체를 구조 파괴하여 1개 이상의 변수에 개별적으로 할당하는 것을 말함

배열 디스트럭처링 할당


  • 할당의 대상(우변) : 이터러블 (이터러블이 아니면, TypeError 발생)
  • 할당 기준 : 배열의 인덱스 (순서대로 할당됨)
    • 변수의 개수와 이터러블의 요소 개수가 반드시 일치할 필요는 없음
    • 변수의 개수 > 이터러블의 요소 개수 -> 할당 받지는 못한 변수는 undefined 임
  • 할당 받을 변수(좌변) : 배열 리터럴 형태로 선언
    • let을 사용하면 변수 선언과 할당을 분리할 수 있음 (비추천)
    • 할당 받을 변수에 기본값 설정 가능 (기본값 보다 할당값이 우선됨)
    • 할당 받을 변수에 Rest 요소(...Rest)를 사용 가능
      • 남은 값의 배열을 이루는 변수가 됨
      • 항상 마지막에 위치해야 함

객체 디스트럭처링 할당


  • 할당의 대상(우변) : 객체 (또는 객체로 평가 될수 있는 표현식인 문자열, 숫자, 배열 등...)
  • 할당 기준 : 프로퍼티 키
    • 순서 상관없이, 선언된 변수 이름과 프로퍼티 키가 일치하면 할당 됨
  • 할당 받을 변수(좌변) : 객체 리터럴 형태로 선언
    • 객체의 프로퍼티 키와 다른 변수 이름으로 프로퍼티 값을 할당 받기 가능
      • 좌변의 프로퍼티 키에 프로퍼티 값으로 사용하고 싶은 이름을 넣으면 됨
    • 할당 받을 변수에 기본값 설정 가능
    • 함수의 매개변수에도 사용 가능
    • 할당 받을 변수에 Rest 요소(...Rest)를 사용 가능
      • 남은 프로퍼티를 가진 객체를 이루는 변수가 됨

  • 배열과 객체 디스트럭처링을 같이 사용 가능
    • 배열안에 객체가 item으로 작용하는 경우
  • 중첩 객체 디스트럭처링 가능