키를 가진 데이터 여러 개를 하나의 엔티티에 저장할 땐 객체
,
컬렉션에 데이터를 순서대로 저장할 땐 배열
을 사용한다.
함수에 객체나 배열을 전달해야 하는 경우가 생기는데 객체나 배열이 저장된 데이터 전체가 아닌 일부만 필요할 경우가 생긴다. 이 때 객체나 배열을 변수로 ‘분해’할 수 있게 해주는 구조 분해 할당 (destructuring assignment) 을 사용할 수 있다.
이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring)를 사용한다.
배열이 어떻게 변수로 분해되는지 알아보자.
// 이름과 성을 요소로 가진 배열
let arr = ["Bora", "Lee"]
// 구조 분해 할당을 이용해
// firstName엔 arr[0]을
// surname엔 arr[1]을 할당하였습니다.
let [firstName, surname] = arr;
alert(firstName); // Bora
alert(surname); // Lee
인덱스를 이용해 배열에 접근하지 않고도 변수로 이름과 성을 사용할 수 있게 된다.
아래와 같이 split
같은 반환 값이 배열인 메서드를 함께 활용해도 좋다.
let [firstName, surname] = "Bora Lee".split(' ');
구조 분해 할당이란 명칭은 어떤 것을 복사한 이후에 변수로 '분해(destructurize)'해준다는 의미 때문에 붙여졌다. 이 과정에서 분해 대상은 수정 또는 파괴되지 않는다.
// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
쉼표를 사용하면 필요하지 않은 배열 요소를 버릴 수 있다.
// 두 번째 요소는 필요하지 않음
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
두 번째 요소는 생략되었지만, 세 번째 요소는 title이라는 변수에 할당된 것을 확인할 수 있다.
할당할 변수가 없기 때문에 네 번째 요소 역시 생략됨.
배열뿐만 아니라 모든 이터러블(iterable, 반복 가능한 객체)
에 구조 분해 할당을 적용할 수 있다.
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
할당 연산자 좌측엔 ‘할당할 수 있는(assignables)’ 것이라면 어떤 것이든 올 수 있다.
아래와 같이 객체 프로퍼티도 가능하다.
let user = {};
[user.name, user.surname] = "Bora Lee".split(' ');
alert(user.name); // Bora
이 메서드와 구조 분해를 조합하면 객체의 키와 값을 순회해 변수로 분해 할당할 수 있다.
let user = {
name: "John",
age: 30
};
// 객체의 키와 값 순회하기
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, age:30이 차례대로 출력
}
맵에도 물론 이 메서드를 활용할 수 있다.
let user = new Map();
user.set("name", "John");
user.set("age", "30");
for (let [key, value] of user) {
alert(`${key}:${value}`); // name:John, then age:30
}
두 변수에 저장된 값을 교환할 때 구조 분해 할당을 사용할 수 있다.
let guest = "Jane";
let admin = "Pete";
// 변수 guest엔 Pete, 변수 admin엔 Jane이 저장되도록 값을 교환함
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane(값 교환이 성공적으로 이뤄졌습니다!)
임시 배열을 만들어 두 변수를 담고, 요소 순서를 교체해 배열을 분해하는 방식을 사용했다. 두 개뿐만 아니라 그 이상의 변수에 담긴 값도 교환 가능하다.
배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때.
이럴 때는 점 세 개 ...를 붙인 매개변수 하나를 추가하면 ‘나머지(rest)’ 요소를 가져올 수 있다.
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// `rest`는 배열입니다.
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
rest
는 나머지 배열 요소들이 저장된 새로운 배열이 된다. rest
대신에 다른 이름을 사용해도 되는데, 변수 앞의 점 세 개(...)와 변수가 가장 마지막에 위치해야 한다는 점은 잊지 말자.
할당하고자 하는 변수의 개수가 분해하고자 하는 배열의 길이보다 크더라도 에러가 발생하지 않는다. 할당할 값이 없으면 undefined
로 취급되기 때문이다.
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
=
을 이용하면 할당할 값이 없을 때 기본으로 할당해 줄 값인 '기본값(default value)'을 설정할 수 있다.
// 기본값
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (배열에서 받아온 값)
alert(surname); // Anonymous (기본값)
복잡한 표현식이나 함수 호출도 기본값이 될 수 있다. 이렇게 기본식으로 표현식이나 함수를 설정하면 할당할 값이 없을 때 표현식이 평가되거나 함수가 호출된다.
아래 예시에선 값이 제공되지 않았을 때만 함수가 호출되므로, prompt
는 한 번만 호출된다.
// name의 prompt만 실행됨
let [surname = prompt('성을 입력하세요.'), name = prompt('이름을 입력하세요.')] = ["김"];
alert(surname); // 김 (배열에서 받아온 값)
alert(name); // prompt에서 받아온 값
구조 분해 할당으로 객체도 분해할 수 있다.
문법 :
let {var1, var2} = {var1:…, var2:…}
//할당 연산자 우축엔 분해하고자 하는 객체를, 좌측엔 상응하는 객체 프로퍼티의 '패턴'을 넣는다.
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
프로퍼티 options.title
과 options.width
, options.height
에 저장된 값이 상응하는 변수에 할당됐다. 순서는 바뀌어도 좋다.
let options = {
title: "Menu",
width: 100,
height: 200
};
// { 객체 프로퍼티: 목표 변수 }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); // 100
alert(h); // 200
분해하려는 객체의 프로퍼티 개수가 할당하려는 변수의 개수보다 많다면 '나머지’를 어딘가에 할당하면 된다.
나머지 패턴(rest pattern)을 사용하면 배열에서 했던 것처럼 나머지 프로퍼티를 어딘가에 할당하는 게 가능하다.
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = 이름이 title인 프로퍼티
// rest = 나머지 프로퍼티들
let {title, ...rest} = options;
// title엔 "Menu", rest엔 {height: 200, width: 100}이 할당됩니다.
alert(rest.height); // 200
alert(rest.width); // 100
객체나 배열이 다른 객체나 배열을 포함하는 경우, 좀 더 복잡한 패턴을 사용하면 중첩 배열이나 객체의 정보를 추출할 수 있다. 이를 중첩 구조 분해(nested destructuring)라고 한다.
아래 예시에서 객체 options
의 size
프로퍼티 값은 또 다른 객체이다. items
프로퍼티는 배열을 값으로 가지고 있다. 대입 연산자 좌측의 패턴은 정보를 추출하려는 객체 options
와 같은 구조를 갖추고 있다.
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// 코드를 여러 줄에 걸쳐 작성해 의도하는 바를 명확히 드러냄
let {
size: { // size는 여기,
width,
height
},
items: [item1, item2], // items는 여기에 할당함
title = "Menu" // 분해하려는 객체에 title 프로퍼티가 없으므로 기본값을 사용함
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
매개변수 모두를 객체에 모아 함수에 전달해, 함수가 전달받은 객체를 분해하여 변수에 할당하고 원하는 작업을 수행할 수 있도록 하자.
// 함수에 전달할 객체
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// 똑똑한 함수는 전달받은 객체를 분해해 변수에 즉시 할당함
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// title, items – 객체 options에서 가져옴
// width, height – 기본값
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
이렇게 똑똑한 함수 매개변수 문법은 구조 분해 할당 문법과 동일합니다.
function({
incomingProperty: varName = defaultValue
...
})
매개변수로 전달된 객체의 프로퍼티 incomingProperty
는 varName
에 할당되고, 만약 값이 없다면 defaultValue
가 기본값으로 사용된다.
참고로 이렇게 함수 매개변수를 구조 분해할 땐, 반드시 인수가 전달된다고 가정되고 사용된다는 점에 유의해야 한다. 모든 인수에 기본값을 할당해 주려면 빈 객체를 명시적으로 전달해야 한다.