📝

11. 구조 분해 할당

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

11-1. 배열 구조 분해 할당

구조 분해 할당 구문에서 배열의 속성을 해체하여 할당하는 배열 구조 분해 할당을 알아보자.

11-1-1. 이전의 배열 분해 할당 방법(ES5)

구조 분해 할당 구문이 없었던 ES5에서는 어떻게 배열을 분해해서 할당했는지 알아보자.
 
// 배열의 인덱스를 활용하여 요소에 접근하는 방법을 쓴다. var categories = ['IT', 'life', 'TIL']; // 할당을 해주고자 하는 배열 // n개의 할당을 해주고자 하면, 가독성을 위해 n줄의 코드 작성이 필요하다. var category1 = categories[0]; var category2 = categories[1]; var category3 = categories[2]; console.log(category1, category2, category3); // IT life TIL
 
위의 코드는 지금도 사용함에 있어서 문법적인 오류가 없는 코드이다. 하지만 더 간결하고 빠르게 작성할 수 있는 구문이 있다면 좋을 것이다.

11-1-2. 배열 변수 할당하기

ES6에서는 배열의 요소들을 새로 선언한 다른 변수에 할당이 가능하다. 위에서는 3줄에 걸쳐 할당을 했었던 구문을 1줄로 처리가 가능해졌다.
 
const categories = ['IT', 'life', 'TIL']; // 할당을 해주고자 하는 배열 // 아래의 두 코드는 동일하게 작동한다. const [category1, category2, category3] = ['IT', 'life', 'TIL']; const [category1, category2, category3] = categories; console.log(category1, category2, category3); // IT life TIL
 
코드에서 알 수 있는 배열 구조 분해 할당의 기본적인 형태는 다음과 같다.
  • 변수의 선언과 할당이 동시에 이루어진다.
  • 새로운 변수의 선언이 배열안에서 이루어지고 있다.
할당연산자(=)를 기준으로 좌측에는 할당을 해주고 싶은 변수들을 배열안에 입력하고, 우측에는 할당을 해줄 배열을 입력한다. 이 때, 우측에는 꼭 배열이 아니어도 되지만 이터러블한 것이 와야한다.
문자열을 이용하여 구조 분해 할당을 해주는 코드는 아래와 같다.
💡
배열, 문자열, Map, Set은 이터러블하다. 객체는 이터러블 하지 않다.
 
const [resume, tech-interview, final-interview] = [true, true, true]; // 가장 흔하게 볼 수 있는 배열 구조 분해 할당 구문이다. const [resume, tech-interview, final-interview] = 'true,true,true'.split(','); // 'true,true,true'.split(',')의 반환값으로 배열이 나온다. const [resume, tech-interview, final-interview] = 'ttt'; // 문자열은 이터러블하다. console.log(resume, tech-interview, final-interview); // 첫번째 할당문 : true true true // 두번째 할당문 : 'true' 'true' 'true' // 세번째 할당문 : 't' 't' 't'
 
💡
엄밀하게는 첫번째 할당 구문에서는 불리언 값이고, 두번째 할당 구문에서는 문자열 ‘true’이다.
할당을 해줄 값이 배열이기 때문에, 배열을 반환하는 메소드의 활용이나 함수 작성을 원활하게 하는 것이 도움이 될 것이다.

11-1-3. 선언 후 변수에 할당하기

선언과 할당을 동시에 해주지 않고, 변수 선언을 먼저 해준 뒤에 할당을 하는 방법도 가능하다.
 
const categories = ['IT', 'life', 'TIL']; // 할당을 해주고자 하는 배열 let category1, category2, category3; // 선언만 해준 상태이므로 undefined [category1, category2, category3] = categories; // IT life TIL
 
선언만을 먼저 해주어야 할 경우에는 반드시 let키워드를 써야한다. const로 해주면 이전에 5단원 ‘var,let,const’ 에서 살펴보았듯이 에러가 발생한다.
 
const categories = ['IT', 'life', 'TIL']; // 할당을 해주고자 하는 배열 const category1, category2, category3; // Missing initializer에러가 발생한다. [category1, category2, category3] = categories; // 에러가 위에서 발생하므로 할당이 불가능!
 

11-1-4. 에러처럼 보이지만 에러가 아닌 구문

  • 좌변에서의 변수의 갯수가 우변의 배열의 요소의 갯수보다 많은 경우
 
const categories = ['IT', 'TIL']; // 배열 요소의 갯수 : 2 const [category1, category2, category3] = categories; // 변수의 갯수 : 3 // category3 === undefined
 
읽기에는 어색하지만, 선언만 해주고 할당을 안해준 경우이기 때문에 undefined가 나오는 것은 당연하다.
 
  • 좌변에서 콤마(,)사이에서 변수를 선언하지 않은 경우
 
const [title, , categories] = ['TIL-JS', '...content...', ['it', 'js', 'til']]; // 원하는 title과 categories의 값만 가져올 수 있다. // title : 'TIL-JS' // categories : ['it', 'js', 'til']
 
이를 활용해서 원하는 값만 가져올 수 있다. 예를 들어, 배열의 특정 인덱스 값이 필요한 경우라면 모든 요소를 할당받지 않고도 원하는 요소만을 선택해서 가져올 수 있다.
더 복잡한 형태에서 원하는 요소만을 가져오는 코드를 살펴보자. 객체 구조 분해 할당의 내용이 있으므로 뒤의 내용을 공부한 후에 보는 것도 좋을 것이다.
 
// 구조분해할당을 통한 복잡 데이터 추출 // todos라는 배열안에 객체가 3개 담겨진 구조이다. // 각각의 객체에는 id, content, completed라는 키 값이 존재한다. const todos = [ { id: 1, content: 'HTML', completed: true }, { id: 2, content: 'CSS', completed: false }, { id: 3, content: 'JS', completed: false } ]; // todos 배열의 두 번째 요소인 객체로부터 id 프로퍼티만 추출 const [, {id}] = todos; console.log(id); // 2 // todos 배열의 세 번째 요소인 객체로부터 content 프로퍼티만 추출 const [,, {content}] = todos; console.log(content); // 'JS'
 
구조 분해 할당을 활용하기 전에는 항상 데이터의 구조를 파악하는 것이 중요하다. 1차원 배열인지, 2차원 배열인지, 객체안에 배열이 있는지, 배열안에 객체가 있는지 등 구조를 먼저 파악하고 데이터를 가져오도록 하자.

11-1-5. 기본 값(default) 설정하기

배열에서 값이 안넘어오는 경우를 대비하여, 기본값 설정도 가능하다. 설정방법은 선언을 해줄 때 할당연산자(=)를 통해 설정한다.
 
const post = ['JS-study', ['til', 'fe', 'it']]; // 기본값을 설정하지 않은 경우 const [title, categories, isPrivate] = post; console.log(isPrivate); // undefined // 기본값을 설정한 경우 const [title, categories, isPrivate = false] = post; console.log(isPrivate); // false
 
위의 코드에선 기본값이 설정되지 않고 값을 받아오지 않아 undefined가 출력되고, 아래에선 isPrivate 값을 받아오지 않았는데도, undefined가 아닌 false로 설정이 된다.
 
const post = ['JS-study', ['til', 'fe', 'it'], true]; const [title, categories, isPrivate = false] = post; console.log(isPrivate); // true
 
기본 값 설정도 되어있고, 값을 할당도 받았다면 할당 받은 값이 적용된다. (기본 값 < 할당받은 값)

11-1-6. 변수 값 교환하기

할당이 이미 된 변수들의 값을 교환해주고 싶을 때 배열 구조 분해 할당을 활용할 수 있다. 해당 변수들의 데이터를 확인하여 따로따로 재할당 하거나, 임시의 변수를 만들어 할당을 해주고 교환을 하는 작업을 해주지 않아도 된다.
 
let wade = { name : 'wade', ranking : '1st', }; let licat = { name : 'licat', ranking : '2nd', }; [wade.ranking, licat.ranking] = [wade.ranking, licat.ranking]; console.log(wade, licat); // {name: 'wade', ranking: '2nd'} {name: 'licat', ranking: '1st'}
 

11-1-7. 배열과 Rest 요소

Rest 요소란 구조 분해 할당에서 선언된 변수에 값을 할당 받을 때, 나머지 요소들을 배열로 받는 것을 의미한다. 사용 방법은 좌측의 변수안에서 마지막 요소로 전개구문( ... )을 사용한다.
 
const categories = ['IT', 'life', 'TIL', 'javascript']; const [firstCategory, ...restCategories] = categories; console.log(firstCategory); // 'IT' console.log(restCategories); // ['life', 'TIL', 'javascript'] 나머지 요소들이 배열로 할당이 된다. // 나머지 요소들의 갯수가 하나이더라도 배열로 받아온다. const categories = ['IT', 'life']; const [firstCategory, ...restCategories] = categories; console.log(firstCategory); // 'IT' console.log(restCategories); // ['life']
 
유의할 점은, 반드시 배열의 마지막에서 Rest요소를 작성해야 한다. 마지막이 아닌 곳에서 사용하면 에러가 발생한다.
 
const categories = ['IT', 'life', 'TIL', 'javascript']; const [firstCategory, ...restCategories, lastCategory] = categories; // Uncaught SyntaxError: Rest element must be last element
 

11-2. 객체 구조 분해

객체의 속성을 해체하여 변수에 할당하는 객체 구조 분해에 대해 알아보자.

11-2-1. 이전의 객체 구조 분해 할당 (ES5)

ES5에서는 객체의 프로퍼티 키를 이용하여 프로퍼티를 변수에 할당한다.
 
// human이라는 객체를 생성한다. const human = {name: 'su', age: 10}; // 객체의 프로퍼티 키(name, age)를 이용하여 변수에 할당한다. const name = human.name; const age = human.age; console.log(name); // 'su' console.log(age); // 10
 

11-2-2. 현재의 객체 구조 분해 할당 (ES6)

ES6에서는 객체 각각의 프로퍼티를 객체로부터 추출해서 변수에 할당한다. 우변에 구조 분해시킬 객체를 놓고 좌변에 값을 할당 받을 변수를 객체 리터럴 형태로 선언해준다.
 
const human = {name: 'su', age: 10}; // 변수를 객체 리터럴 형태로 선언하여 human 객체의 프로퍼티를 구조 분해하여 할당한다. const {name, age} = human; console.log(name); // 'su' console.log(age); // 10
 
객체 구조 분해 할당은 프로퍼티 키를 기준으로 할당된다. 따라서 할당 받을 변수의 이름과 프로퍼티 키가 동일해야 한다.
 
const human = {name : 'su', age: 10}; // 할당 받을 변수명(birth)과 객체의 프로퍼티 키(age)가 동일하지 않다. const {name, birth} = human; console.log(name); // 'su' // birth라는 변수 선언 후 객체의 프로퍼티가 할당되지 않았다. console.log(birth); // undefined
 
구조 분해 할당의 기준이 프로퍼티 키이므로 변수를 작성하는 순서에 영향을 받지 않는다.
 
const candy = {lemon: 'sour', grape: 'sweet'}; // 변수가 작성된 순서와 객체의 프로퍼티 키 순서가 다르다. const {grape, lemon} = candy; console.log(lemon); // 'sour' console.log(grape); // 'sweet'
 

11-2-3. 새로운 변수를 이용한 할당

정해진 프로퍼티 키와 동일한 변수가 아닌 새로운 변수를 이용하여 객체를 구조 분해 할당시킬 수 있다.
콜론(:) 을 사용하여 {프로퍼티 키: 새로운 변수명} 형태로 할당시킨다.
 
const myPet = {nickname: 'star', age: 8}; // {프로퍼티 키: 새로운 변수명} 형태로 작성한다. const {nickname: 이름, age: 나이} = myPet; console.log(이름); // 'star' console.log(나이); // 8 // 새로운 변수에 할당했으므로 기존의 프로퍼티 키와 동일한 변수는 존재하지 않는다. console.log(nickname); // ReferenceError: nickname is not defined console.log(age); // ReferenceError: age is not defined
 

11-2-3. 기본값 설정

구조 분해 할당시키려는 객체의 프로퍼티가 없을 경우를 대비하여 기본값을 설정할 수 있다. 할당연산자(=) 를 사용하여 {프로퍼티 키 = 기본값}의 형태로 프로퍼티의 기본값을 정한다.
 
const girl = {name: 'su'}; // 객체에 존재하지 않는 프로퍼티(age, feature)의 기본값을 설정한다. const {name, age = 10, feature = 'cute'} = girl; console.log(name); // 'su' console.log(age); // 10 console.log(feature); // 'cute'

11-2-4. 기본값을 가진 새로운 변수를 이용한 할당

프로퍼티 키와 동일한 변수가 아닌 새로운 변수를 이용하면서 프로퍼티의 기본값을 설정할 수 있다. 할당연산자(=), 콜론(:) 을 사용하여 {프로퍼티 키: 새로운 변수명 = 기본값} 형태로 작성한다.
 
const human = {name: 'su'}; const {name, height: h = 175} = human; console.log(name); // 'su' console.log(h); // 175 // h라는 새로운 변수에 할당했으므로 height에는 프로퍼티가 할당되지 않는다. console.log(height); // ReferenceError: height is not defined

11-2-5. 객체와 Rest 프로퍼티

할당하려는 변수의 개수가 분해 될 객체의 프로퍼티 개수보다 적은 경우가 있다. 개수의 차이로 인해 분해된 객체의 프로퍼티를 각각의 변수에 전부 담지 못하여 나머지가 생기게 된다. 이 경우, 변수에 담기지 못한 나머지 객체 프로퍼티를 모아서 할당할 수 있다.
전개구문(...) 을 사용하여 값을 할당 받을 변수의 가장 마지막에 ...변수명 형태로 작성한다.
 
const myBag = {laptop: 'black', card : 'blue', powder : 'white', lipstick : 'red'}; const {laptop, card, ...cosmetics} = myBag; console.log(laptop); // 'black' console.log(card); // 'blue' console.log(cosmetics); // {powder: 'white', lipstick: 'red'}

11-3. 중첩 구조 분해

객체나 배열이 다른 객체 및 배열을 포함하는 경우의 중첩된 객체 및 배열의 정보를 구조 분해 할당하여 추출할 수 있다. 이를 중첩 구조 분해(nested destructuring)라고 한다.
 
아래 예시는 객체 bread 안에 여러 개의 프로퍼티가 있다. info는 또 다른 객체이다. info 객체 안에 type 프로퍼티는 배열이다. ingredients는 2차원 배열이다.
 
const bread = { title : '빵', info : { make : true, type : ['바게뜨', '호밀빵', '핫도그 번', '도넛', '브래드스틱', '난', '비스킷'], }, ingredients: ['ingredients', ['밀가루', '이스트', '사워도', '소금']], }
 
bread라는 객체에서 type의 배열, ingredients '밀가루', '이스트', '소금' 을 추출해야 할 경우, 해당 값을 한번에 추출하는 방법을 알아보자.
 
const { ingredients : [ , [ // ingredients 2차원 배열 추출할 값 할당 ingredient1, ingredient2, , ingredient3 ]], info : { // info가 객체이므로 {} 으로 접근 type : breadType // type 변수 이름 'breadType'으로 변경 } } = bread; console.log(ingredient1); // '밀가루' console.log(ingredient2); // '이스트' console.log(ingredient3); // '소금' console.log(breadType); // ['바게뜨', '호밀빵', '핫도그 번', '도넛', '브래드스틱', '난', '비스킷']

11-4. for of 반복문과 구조 분해

for of 반복문과 메서드를 사용하여 구조분해 할당을 사용해보자.

11-4-1. Object.entries(), for of

Object.entries()와 반복문을 사용하면 객체의 키와 값을 순회하여 변수로 분해 할당을 할 수 있다.
 
const site = { name : 'paullab', url : 'www.paullab.co.kr', mobile : 'm.paullab.co.kr' }; for (const [key, val] of Object.entries(site)) { console.log(`${key}: ${val}`); }
 

11-4-2. Map(), for of

Map()과 반복문을 사용해서 객체의 키와 값을 순회하여 변수로 분해 할당 할 수 있다.
 
const site = new Map(); site.set("name", "paullab"); // set으로 객체에 삽입 site.set("url", "www.paullab.co.kr"); for (const [key, val] of site) { console.log(`${key}: ${val}`); }
 

11-5. 함수 구조 분해

11-5-1. 함수 매개변수의 기본값(default) 설정

ES5 , 함수 매개변수가 없을 경우 기본값을 조건문으로 설정해야 했다.
 
function person(user) { user = user === undefined ? {} : user; let name = user.name === undefined ? 'X' : user.name ; let age = user.age === undefined ? 'X' : user.age; console.log(`이름 : ${name}, 나이 : ${age}`); } person({ name : 'kimcoding', age : 30 }); // 이름 : kimcoding, 나이 : 30
 
ES6, 함수 매개변수의 기본 값을 설정을 할 수 있게 되어 편리하게 사용할 수 있게 되었다. 아래 예시 shape 함수는 구조 분해된 좌변을 우변의 빈 객체에 할당하여 사용하였다.
{name = 'X', age = 'X', favoriteColor = 'black' }={}
또 다른 방법으로 빈 객체를 우변에 할당하지 않고도 사용할 수 있다. 하지만 호출 시 적어도 하나의 인수가 제공되어야 한다. shape() 함수는 어떤 매개변수 없이도 호출 하길 바란다면 아래의 형태가 유용하고, 객체를 넘기길 원할 경우에는 함수 호출 시 객체를 넘겨주면 된다.
 
function person({name = 'X', age = 'X', favoriteColor = 'black' }={}) { console.log(`이름 : ${name}, 나이 : ${age}, 좋아하는 색상 : ${favoriteColor}`); } person({ name : 'kimcoding', }); // 이름 : kimcoding, 나이 : X, 좋아하는 색상 : black
 
기본값을 할당해 주려면 빈 객체를 전달해야 한다. 인수가 없을 경우 에러가 발생한다.
 
function person({name = 'X', age = 'X', favoriteColor = 'black' }) { console.log(`이름 : ${name}, 나이 : ${age}, 좋아하는 색상 : ${favoriteColor}`); } person({}); // 이름 : X, 나이 : X, 좋아하는 색상 : black function person({name = 'X', age = 'X', favoriteColor = 'black' }) { console.log(`이름 : ${name}, 나이 : ${age}, 좋아하는 색상 : ${favoriteColor}`); } person();
notion imagenotion image
 
에러를 발생하지 않게 하려면, 매개변수 기본값을 사용하기 위해 전체를 감싼 객체를 좌변으로 보내고 우변에 빈 객체를 넣어 값을 읽을 수 있게 되었다.
 
function person({name = 'X', age = 'X', favoriteColor = 'black' }={}) { console.log(`이름 : ${name}, 나이 : ${age}, 좋아하는 색상 : ${favoriteColor}`); } person(); // 이름 : X, 나이 : X, 좋아하는 색상 : black
 
매개변수로 객체를 넣어 해당 객체의 값을 추가하거나 변경하고 싶을 경우에는 아래의 예시와 같이 하는데 shape 함수를 호출하는 코드를 보면서 앞의 예시와 다른점을 비교해보자.
 
function shape({color = 'puple', cords = {x:0, y: 0}, radius = 10 } = circle) { circle = { color, cords, radius } } shape(circle = { cords : { x : 10, y : 20 }, }); console.log(circle);
notion imagenotion image
 

11-5-2. 함수 매개변수로 전달된 객체에서 속성 분해 할당

아래 예제는 movie 객체에서 함수를 사용하여 속성 id, cartegory, title를 분해하고 할당한다.
 
function movieId({id}) { return id; } function whatMovie ({category: genre, title: {main: name}}){ console.log(`${genre[0]} movie is ${name}`); } const movie = { id: 14, category: ['SF', 'Fantasy'], title: { main: "듄", sub: "이것이 위대한 시작이다" } }; console.log("movieId: " + movieId(movie)); // "movieId: 14" whatMovie(movie); // "SF movie is 듄"

11-5-3. 구조 분해 문제

최연소 찾기
  • people 객체의 이름과 나이의 정보가 담겨있다. 가장 나이가 적은 사람의 이름과 나이를 반환하는 함수를 만들어보자. (구조 분해 할당을 사용하여 문제를 풀어보자.)
    • Object.entries와 구조 분해를 사용해서 문제를 풀어보세요.
    •  
const people = { roberst : 20, mark : 18, kevin : 7, james : 21, michael : 11, mark : 15 };
 
  • 정답 코드
 
function findYoungest(people) { let name = ''; let age = Number.MAX_SAFE_INTEGER; // 안전한 최대 정수값 초기화 설정 let result; for ([key, val] of Object.entries(people)) { if (age > val) { age = val; name = key; } } result = `이름: ${name} 나이 :${age}` return result; }
 
for of 반복문과 Object.entries를 사용하여 key와 value를 분해한 뒤 조건문으로 age보다 val가 작을 경우 age의 val의 값을 넣는다. 마지막에는 제일 작은 값이 남게 된다.
 
 

References


  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment