ES6 문법 정리 시리즈2 (destructuring, default+rest+spread)

WooSeong·2021년 5월 16일
0

학습 노트

목록 보기
19/22

구조 분해 할당(destructuring)

구조 분해 할당은 배열이나 객체의 속성을 해체하여(구조 분해) 그 값을 개별 변수에 담을 수 있게 하는(할당) JavaScript 표현식 입니다.

구조 분해 할당을 보면 일반적인 자바스크립트 표현식과 좌변 우변이 반대로 되어 있는 모습을 하고 있다.

let arr = [1, 2, 3, 4, 5]; // 일반적인 표현식

let [a, b, c] = arr; // 구조 분해 할당

console.log(a) // 1
console.log(b) // 2
console.log(c) // 3

자바스크립트 표현식에서 할당(혹은 재할당)을 의미하는 첫번째 표현식을 살펴보면 구조 분해 할당의 의미에 대해 좀더 이해하기 쉽다.

  • arr이라는 변수를 선언하고 배열에 1, 2, 3, 4, 5라는 값을 담아 arr에 할당한다.
    • 담을 그릇 : arr
    • 담을 물건 : 배열
  • 구조 분해 할당의 경우 ⇒ 담을 그릇의 형태를 규정해 주고(배열) arr에서 동일 인덱스의 값들을 빼온다.
    • 담을 그릇 : [a, b, c]
    • 담을 물건 : arr

배열이나 객체에 가져 오고 싶은 값이 어디 있는지 파악하고 있고 가져올 대상의 크기가 크다면 구조 분해 할당은 매우 유용하다. 대상의 크기가 작더라도 직접 참조보다 코드가 간결해 진다.

배열의 구조 분해 할당

배열은 각 값들을 인덱스를 통해 관리한다. 인덱스를 임의로 바꿀 순 없으며 배열로 관리 되는 데이터는 '순서'만 의미를 갖는다. 배열의 구조 분해 할당을 할때는 원하는 값이 배열의 몇번째 순서에 있는지가 중요 하다.

기본 변수 할당, 선언에서 분리한 할당

배열의 기본 변수 할당은 다음과 같다.

let arr = [1, 2, 3, 4, 5];

let [a, b, c] = arr;
//혹은
let a, b, c; // 선언은 할당과 분리하여 따로 한다.
[a, b, c] = arr; // 선언에서 분리한 할당

console.log(a) // 1
console.log(b) // 2
console.log(c) // 3

구조 분해 할당의 선언과 할당을 동시에 할 수 있으며, 선언과 할당을 분리하여 할 수도 있다.

할당에 기본값 주기(default parameter)

구조 분해 할당을 할때 기본값을 줄 수 있다. 분해한 값이 undefined일때 해당 값을 대신하여 기본 값을 사용하여 할당한다.

let arr = [1, 2];
let [a = 7, b = 3, c = 10] = arr;

console.log(a) // 1
console.log(b) // 2
console.log(c) // 10 undefined가 아님!

변수 값 교환하기

두 변수의 값을 서로 교환 할때 구조 분해 할당을 사용하면 임시 변수 없이 교환이 가능하다. 의미 없는 변수의 사용을 줄일 수 있다!

let a = 1;
let b = 2;

[a, b] = [b, a]

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

특정 반환 값 무시하기

필요 하지 않은 반환 값을 무시 할 수 있다.

const f = () => [1, 2, 3];

let [a, ,b] = f();

console.log(a) // 1
console.log(b) // 3

//반환값을 모두 무시 할 수도 있다.

변수에 배열의 나머지를 할당하기(rest parameter)

rest parameters를 이용하여 배열의 나머지 값들을 하나의 변수에 할당 할 수 있다. 이때 rest parameter는 가장 마지막에 위치해야 한다. rest parameter가 중간에 들어가 있으면 SyntaxError가 발생한다.

let [a, ...b] = [1, 2, 3];

console.log(a) // 1
console.log(b) // [2, 3] 배열이 변수에 담기는 것을 볼 수 있다.

객체의 구조 분해 할당

객체는 키-값 쌍으로 이루어진 데이터이다. 배열과의 가장 큰 차이점 이라면 순서를 몰라도 키를 알고 있다면 원하는 값에 바로 접근 할 수 있다는 점이다. 배열처럼 새로운 변수에 구조 분해 할당 할 수 있지만, 구조 분해 할당 을 수행할때 정확한 키를 알아야 한다.

기본 할당(기본 '변수' 할당이 아니다!)

객체를 구조 분해 할당 하면 중첩된 객체에 특정 키-값에 접근하는게 매우 편리해 진다. 3중첩된 특정 키값으로 접근하기 위해 someObject.first.second.youFindMe 같은 식의 길게 늘어진 dot notation을 사용할 필요가 없어진다. 이는 가독성을 많이 향상 시킨다!

let obj = {a: 4, b: 'apple'};
let {a, b} = obj;

console.log(a) // 4
console.log(b) // 'apple'

//특정 값에 접근하기 위해 obj.a 나 obj.b로 접근 하지 않아도 되는것에 유의하자!

선언 없는 할당

배열의 구조 분해 할당의 선언과 분리된 할당과 비슷한 개념이다. 몇가지 차이가 있는데 아래 예제를 통해서 알아보자.

let a, b; 
// 선언 없는 할당 앞에는 세미콜론이 붙어야 한다 만일 세미콜론이 없으면 함수의 실행으로 인식한다.

({a, b} = {a: 4, b: 'apple'});
//할당은 소괄호 안에서 이루어 져야 한다! ( .. )

console.log(a) // 4
console.log(b) // 'apple'

새로운 변수 이름으로 할당하기

객체 리터럴에 새로운 키-값을 넣는것과 같이 보여 혼동할 수 있지만, 해당하는 키의 값을 새로운 변수에 할당하는 방법이다!

let obj = {a: 4, b: 'apple'};
let {a: foo, b: bar} = obj;

console.log(foo) // 4
console.log(bar) // 'apple'

기본값(default parameter), 새로운 변수 이름으로 할당하면서 기본값 지정해주기

객체로부터 해체된 값이 undefined일 경우에 기본 값으로 할당된 값이 undefined 대신 사용된다.

let {a = 8, b = 'banana'} = {a: 4};

console.log(a) // 4
console.log(b) // 'banana'

//새로운 변수 이름으로 할당과 기본값 지정을 동시해 해주면
let{a: foo = 8, b: bar = 'banana'} = {a: 4};

console.log(foo) // 4
console.log(bar) // 'banana'

함수 매개변수의 기본값 설정하기

MDN의 예제를 통해 알아보자

function drawES2015Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
  console.log(size, cords, radius);
  // 차트 그리기 수행
}

drawES2015Chart({
  cords: { x: 18, y: 30 },
  radius: 30
});
  • cords와 radius는 매개변수로 받은 { x: 18, y: 30 }, 30으로 할당되지만 size는 매개변수로 받지 않았기 때문에 기본값인 'big'으로 설정된다.
  • 매개변수의 기본값을 할당할 때 '구조 분해 된' 좌변에 빈 객체 리터럴을 할당하는 것을 볼 수 있다.
    • 빈 오브젝트를 할당 하면 어떤 매개변수 없이도 호출이 가능하다
      • drawES2015Chart();
    • 빈 오브젝트의 할당 없이도 가능하다.
      • 빈 오브젝트를 할당하지 않으면 호출시 적어도 하나의 인자가 제공 되어야 한다.

중첩된 객체 및 배열의 구조 분해

MDN 예제를 통해 알아보자

var metadata = {
    title: "Scratchpad",
    translations: [
       {
        locale: "de",
        localization_tags: [ ],
        last_edit: "2014-04-14T08:43:37",
        url: "/de/docs/Tools/Scratchpad",
        title: "JavaScript-Umgebung"
       }
    ],
    url: "/en-US/docs/Tools/Scratchpad"
};

var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle);  // "JavaScript-Umgebung"
  • metadata 객체에서
    • title 값을 가져와 englishTitle 변수에 할당해 주었다.
    • translations의 값을 가져온다.
      • 해당 값은 배열이다. 2개의 요소로 이루어진 배열
      • 배열의 첫번째 인덱스 값만 가져온다. 두번째 인덱스는 무시된다.
      • 첫번째 인덱스의 값은 객체다.
      • 객체중 title 값을 가져와 localeTitle 변수에 할당해 주었다.

for of 반복문과 구조 분해

배열에 담긴 객체라면 for of 반복문을 통해 배열 내부를 순회하면서 동일 구조의 객체를 반복적으로 구조 분해 할당 할 수 있다. MDN 예제를 보도록 하자

var people = [
  {
    name: "Mike Smith",
    family: {
      mother: "Jane Smith",
      father: "Harry Smith",
      sister: "Samantha Smith"
    },
    age: 35
  },
  {
    name: "Tom Jones",
    family: {
      mother: "Norah Jones",
      father: "Richard Jones",
      brother: "Howard Jones"
    },
    age: 25
  }
];

for (var {name: n, family: { father: f } } of people) {
  console.log("Name: " + n + ", Father: " + f);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"
  • people 배열은 객체를 요소로 가지는 배열이다. 배열의 각 요소 객체는 동일한 구조를 가지고 있다.
  • for of 반복문을 통해 people 배열을 순회한다.(순서대로)
    • 구조 분해 할당을 통해 동일 구조의 여러 값들을 순차적으로 가져 올 수 있다.

객체 구조 분해에서 rest parameters

배열은 rest를 배열의 형태로 받았다면 객체는 rest를 객체의 형태로 받는다!

let {a, b, ...rest} = {a: 4, b: 'banana', c: 7, d: 8}
a; // 4
b; // 'banana'
rest; // { c: 7, d: 8 }

default, rest, spread

Default parameters

기본값 함수 매개변수를 사용하면 전달되는 인자가 없거나 undefined가 전달된 경우 기본값으로 제공된 값으로 대체하여 사용할 수 있다.

JavaScript에서 함수의 매개변수는 undefined가 기본 이다. 특정 상황에서 undefined말고 다른 값을 기본값으로 할당하고 싶을 경우 함수 내에서 매개변수 값을 검사하여 undefined 일 경우 값을 할당 하는 것 뿐이었다.

이런 방법은 코드를 길어지게 만들고 가독성을 저하 시킨다. Default parameters를 통해 함수 선언시 매개변수 자체에 기본값을 설정해 주어 코드를 한층 간결하게 작성할 수 있게 되었다.

//매개변수 b = 1이 바로 Default parameters b에 기본값 1을 할당해 준다는 의미이다.
const multiply = (a, b = 1) => a * b

multiply(5) // return 5

//2개의 매개변수가 선언된 함수에 전달인자를 하나만 줬으나 b가 undefined가 아닌 1로 대체 되면서
//연산이 올바르게 진행 되었다.

//만일 Default parameters를 사용하지 않았다면 5 * undefined 즉 NaN이 반환된다.

앞쪽 매개변수는 뒷쪽 매개변수의 기본값으로 사용할 수 있다.

매개변수가 여러개 일경우 앞쪽의 매개변수를 뒷쪽의 매개변수의 기본값으로 사용할 수 있다. MDN 예제를 통해 알아보자

function greet(name, greeting, message = greeting + ' ' + name) {
  return [name, greeting, message]
}

greet('David', 'Hi')                      // ["David", "Hi", "HiDavid"]
greet('David', 'Hi', 'Happy Birthday!')   // ["David", "Hi", "Happy Birthday!"]
  • 앞쪽 매개변수 name과 greeting이 뒷쪽 message의 기본값으로 바로 사용 되었다. 게다가 기본값에 표현식을 줄수도 있다!

기본값 할당 있는 해체된 매개변수

Default parameters를 Destructuring assignment와 함께 사용할 수 있다. MDN 예제를 통해 알아보자

function f([x, y] = [1, 2], {z: z} = {z: 3}) {
  return x + y + z
}

f()  // 6

Rest parameters vs Spread syntax

두 문법 모두 ...을 사용하기 때문에 구분이 필요하다. rest '파라미터'는 함수의 매개변수에 사용하고 전개 구문은 배열이나 객체에 사용한다.

Rest parameters

JavaScript의 ...은 정말 유용하다. 같게 생긴 ...도 어디에 오느냐에 따라 의미가 달라진다. 우선 첫번째 ...을 보도록 하자!

Rest 파라미터는 전달인자가 몇개가 될지 모를때 전달 받은 모든 인자를 배열로 나타낼 수 있게 해준다.

마지막 파라미터만 rest 파라미터가 될 수 있다!

정해진 매개변수와 부정수 매개변수를 동시에 사용하기

const consoleLog = (a, b, ...rest) => {
	console.log(a);
	console.log(b);
	consolo.log(rest);
}

consoleLog(1, 2, 3, 4, 5, 6, 7);
//1
//2
//[3, 4, 5, 6, 7]

특정 매개변수는 '정해져' 있고 나머지는 몇개의 전달인자가 들어올지 모르는 상황에서 유용하다! rest의 각 요소들은 인덱스를 통해 접근할 수 있다.

rest parameters 와 arguments 객체의 차이

arguments 객체는 실제 배열이 아니다. 그러나 rest 파라미터는 Array 인스턴스 이기 때문에 배열의 메서드를 사용 가능하다.

rest 파라미터 구조 분해 할당

rest 파라미터는 배열이기 때문에 해체될 수 있다(배열로만) 즉 구조 분해 할당을 사용할 수 있다. MDN 예제를 통해 알아보자

//모든 전달인자를 rest parameter로 받음
//전달 받은 배열의 0, 1, 2번 인덱스를 a, b, c 변수에 담음! => 구조 분해 할당
function f(...[a, b, c]) {
  return a + b + c;
}

f(1)          // NaN (b 와 c 는 undefined)
f(1, 2, 3)    // 6
f(1, 2, 3, 4) // 6 (4번 째 파라미터는 해체되지 않음)

Spread Operator(Spread Syntax)

전개 구문은 ...의 두번째 사용 방법이다. 배열이나 문자열과 같이 iterable한 요소를 가진 데이터를 확장 할 수 있다. 객체의 경우는 ECMAScript stage 4에 이르러 객체 리터럴의 속성 전개가 추가 되었다.

원래 그릇에 담겨져 있는 달걀을 하나하나 꺼내어 다른 그릇에 담는 것을 생각해 보자! 전개 구문이 이와 같은 일을 해준다.

전개 구문은 배열을 조작하는데 특히 강력한 모습을 보여준다.

함수 호출에서의 전개

  • apply() 대체

    • 배열의 엘리먼트를 함수의 인수로 사용하고자 했을때는 Function.prototype.apply()를 사용 하였지만, 전개 구문을 사용하면 훨씬 간결하게 적용할 수 있다.

      const func = (x, y, z) => {};
      let args = [1, 2, 3]
      func(...args); // 1, 2, 3이 x, y, z로 전달 된다!
  • new에 적용

    • new를 사용해 생성자를 호출 할 때 배열과 apply를 직접 사용하는 것은 불가능 하였다!

    • 전개 구문을 활용하면 직접적으로 new와 함께 사용 가능하다.

      var dateFields = [1970, 0, 1];  // 1 Jan 1970
      var d = new Date(...dateFields); 
      
      //MDN 예제

배열 리터럴의 전개 구문

  • 배열 복사

    • 기존의 배열을 복사하기 위한 방법으로는 slice()를 이용하는 방법이 있었다.

    • 전개 구문을 활용하면 훨씬 간결하게 배열을 복사 할 수 있다.

    • arr.slice()와 유사하기 때문에(새로운 배열을 반환) immutable한 복사가 가능하다.

      let arr = [1, 2, 3];
      let copied = [...arr];
      
      arr.push(4);
      
      console.log(arr) // [1, 2, 3, 4]
      console.log(copied) // [1, 2, 3]
  • 배열 연결

    • concat()을 대체 할 수 있으며 unshift()도 대체 할 수 있다.

    • 특히 unshift()의 경우와 달리 전개 구문은 새로운 배열을 반환 하므로 원본 배열을 변형하지 않는다!

      let arr1 = [1, 2, 3];
      let arr2 = [4, 5, 6];
      
      //concat 대체
      let arr1 = [...arr1, ...arr2];
      
      //unshift 대체
      let arr1 = [...arr2, ...arr1];

객체 리터럴의 전개 구문

객체 리터럴도 배열 리터럴과 비슷하게 전개 구문을 사용할 수 있다. 단 주의해야 할 사항이 있다!

객체 리터럴에 전개 구문을 사용해 배열에 담는 것은 불가능 하다. 객체는 iterable 하지 않기 때문이다.

let obj = {a: 1, b: 2};
let arrTransObj = [...obj]; // TypeError 발생 obj is not iterable

//반대의 경우는 가능하다! 배열은 iterable 하기 때문
let arr = ['a', 'b', 'c'];
let objTransArr = {...arr};
objTransArr; // {0: 'a', 1: 'b', 2: 'c'} 인덱스가 자동으로 키값이 되었다.
  • 객체의 복사

    • 전개 구문을 사용하여 객체를 복사 할 수 있다!

      let obj = {a: 1, b: 2}
      let copied = {...obj};
      copied; // {a: 1, b: 2}
  • 객체 내부 키-값의 수정 (중첩된 객체를 통한 예제)

    • 객체의 복사와 동시에 특정 키의 값을 재할당(수정) 해 줄 수 있다.

      let banana = {
      	type: 'fruit',
      	color: 'yellow',
      	shape: {
      		tip: 'pointy'
      		body: 'round and slim'
      	},
      	taste: 'sweet'
      }
      
      let 참외 = {
      	//바나나의 속성을 모두 복사해 온다.
      	...banana,
      	//수정해 줄 속성을 따로 적어준다.
      	shape: { ...shape, tip: 'flat', body: 'round and fat' }
      }
      
      참외; 
      /* {
      			type: 'fruit',
      			color: 'yellow',
      			shape: {
      				tip: 'flat'
      				body: 'round and fat'
      			},
      			taste: 'sweet'	
      	 } */
    • 속성 수정시 값에 변수형태를 넣으면 안된다! ({ ...shape, tip: flat, body: round and fat }) 이는 객체 구조 분해 할당에서 새로운 변수에 값을 담는 것과 동일한 형태이기 때문이다.

profile
성장하는 개발자를 꿈꿉니다

0개의 댓글