구조 분해 할당
구조 분해 할당 (Destructuring assignment)
배열에서 값을, 또는 객체에서 속성(property)을 꺼내 별개의 변수에 담을 수 있게 해 주는 JavaScript 문법입니다. 데이터를 받아들이는 위치(예: 할당문의 왼쪽, 또는 새로운 식별자 바인딩을 만드는 모든 곳)에서 사용할 수 있습니다. Baseline Widely available — 이 기능은 널리 지원되며 수많은 기기와 브라우저 버전에서 동작합니다.
본문
구조 분해(destructuring)는 배열의 값이나 객체의 속성을 개별 변수로 풀어내는 JavaScript 문법입니다. 값을 받는 위치(할당문의 왼쪽이나 새로운 식별자 바인딩이 생기는 곳) 어디에서든 사용할 수 있습니다.
시도해 보기 (Try it)
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// Expected output: 10
console.log(b);
// Expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// Expected output: Array [30, 40, 50]
구문 (Syntax)
const [a, b] = array;
const [a, , b] = array;
const [a = aDefault, b] = array;
const [a, b, ...rest] = array;
const [a, , b, ...rest] = array;
const [a, b, ...{ pop, push }] = array;
const [a, b, ...[c, d]] = array;
const { a, b } = obj;
const { a: a1, b: b1 } = obj;
const { a: a1 = aDefault, b = bDefault } = obj;
const { a, b, ...rest } = obj;
const { a: a1, b: b1, ...rest } = obj;
const { [key]: a } = obj;
let a, b, a1, b1, c, d, rest, pop, push;
[a, b] = array;
[a, , b] = array;
[a = aDefault, b] = array;
[a, b, ...rest] = array;
[a, , b, ...rest] = array;
[a, b, ...{ pop, push }] = array;
[a, b, ...[c, d]] = array;
({ a, b } = obj); // parentheses are required
({ a: a1, b: b1 } = obj);
({ a: a1 = aDefault, b = bDefault } = obj);
({ a, b, ...rest } = obj);
({ a: a1, b: b1, ...rest } = obj);
설명 (Description)
객체와 배열 리터럴 표현식은 임시로 데이터를 묶는(package) 쉬운 방법을 제공합니다.
const arr = [a, b, c];
구조 분해는 이와 비슷한 문법이지만 할당문의 왼쪽에서 사용합니다. 배열 선언의 역연산을 수행하여, 컬렉션의 각 요소를 별개의 변수로 선언합니다.
const arr = [1, 2, 3];
const [a, b, c] = arr;
// a = 1, b = 2, c = 3
객체의 경우, 아래 두 줄의 쌍을 비교해 보면 각 쌍 안에 직접적인 대응 관계가 있음을 알 수 있습니다.
const obj = { a, b, c };
const { a, b, c } = obj;
// Equivalent to:
// const a = obj.a, b = obj.b, c = obj.c;
const obj = { prop1: x, prop2: y, prop3: z };
const { prop1: x, prop2: y, prop3: z } = obj;
// Equivalent to:
// const x = obj.prop1, y = obj.prop2, z = obj.prop3;
이 기능은 Perl이나 Python 같은 언어에 있는 기능과 유사합니다. 배열/객체 구조 분해에 특화된 기능은 아래의 각 예제를 참고하세요.
바인딩 패턴과 할당 패턴 (Binding and assignment)
객체 및 배열 구조 분해 모두 두 종류의 패턴이 있습니다: 바인딩 패턴(binding pattern) 과 할당 패턴(assignment pattern) 입니다. 두 패턴은 구문이 조금 다릅니다.
바인딩 패턴에서는 패턴이 선언 키워드(var, let, const)로 시작합니다. 이후 각각의 속성은 변수에 바인딩되거나 더 구조 분해되어야 합니다.
const obj = { a: 1, b: { c: 2 } };
const {
a,
b: { c: d },
} = obj;
// Two variables are bound: `a` and `d`
모든 변수는 같은 선언을 공유하므로, 일부 변수는 재할당 가능하게 하고 다른 변수는 읽기 전용으로 만들고 싶다면 두 번(한 번은 let으로, 한 번은 const로) 구조 분해해야 할 수 있습니다.
const obj = { a: 1, b: { c: 2 } };
const { a } = obj; // a is constant
let {
b: { c: d },
} = obj; // d is re-assignable
언어가 변수를 자동으로 바인딩해 주는 다른 많은 문법(예: for...in, for...of, for await...of 루프의 루프 변수, 함수 매개변수, catch 바인딩 변수)에서도 바인딩 구조 분해 패턴을 사용할 수 있습니다.
할당 패턴에서는 패턴이 키워드로 시작하지 않습니다. 각 구조 분해된 속성은 할당 대상(target)에 할당됩니다. 이 대상은 var나 let으로 미리 선언되었거나 다른 객체의 속성일 수 있으며, 일반적으로 할당 표현식의 왼쪽에 올 수 있는 무엇이든 가능합니다.
const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj);
// The properties `a` and `b` are assigned to properties of `numbers`
참고: 선언 없이 객체 리터럴 구조 분해를 사용할 때는 할당문 주위의 괄호
( ... )가 필수입니다.{ a, b } = { a: 1, b: 2 }는 단독 문법으로 유효하지 않은데, 왼쪽의{ a, b }가 표현식 문(expression statement) 규칙에 따라 객체 리터럴이 아니라 블록으로 간주되기 때문입니다. 반면({ a, b } = { a: 1, b: 2 })는 유효하며,const { a, b } = { a: 1, b: 2 }도 유효합니다.코딩 스타일에 문장 끝 세미콜론을 쓰지 않는다면,
( ... )표현식 앞에 세미콜론을 넣어 앞의 줄에서 함수를 실행하는 것으로 해석되지 않게 해야 합니다.
위 코드의 바인딩 패턴에 해당하는 문법은 유효하지 않습니다:
const numbers = [];
const obj = { a: 1, b: 2 };
const { a: numbers[0], b: numbers[1] } = obj;
// This is equivalent to:
// const numbers[0] = obj.a;
// const numbers[1] = obj.b;
// Which definitely is not valid.
할당 패턴은 할당 연산자의 왼쪽으로만 사용할 수 있습니다. +=나 *= 같은 복합 할당 연산자에는 사용할 수 없습니다.
기본값 (Default value)
각 구조 분해된 속성은 기본값(default value) 을 가질 수 있습니다. 기본값은 해당 속성이 존재하지 않거나 값이 undefined일 때 사용됩니다. 값이 null일 때는 사용되지 않습니다.
const [a = 1] = []; // a is 1
const { b = 2 } = { b: undefined }; // b is 2
const { c = 2 } = { c: null }; // c is null
기본값은 어떤 표현식이든 될 수 있으며, 필요할 때만 평가됩니다.
const { b = console.log("hey") } = { b: 2 };
// Does not log anything, because `b` is defined and there's no need
// to evaluate the default value.
나머지 속성과 나머지 요소 (Rest properties and rest elements)
구조 분해 패턴의 끝을 나머지 속성 ...rest로 끝맺을 수 있습니다. 배열 구조 분해에서는 이터러블(iterable)의 나머지 요소들을 rest(또는 원하는 이름)라는 새 배열로 모읍니다. 객체 구조 분해에서는 패턴이 이미 잡아내지 않은 객체의 모든 열거 가능한 고유 속성을 rest라는 새 객체로 복사합니다.
형식적으로는 ...rest 구문이 배열 구조 분해에서는 "rest 요소(rest elements)", 객체 구조 분해에서는 "rest 속성(rest properties)"이라 불리지만, 보통 통틀어 "rest 속성"이라고 부릅니다.
const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(others); // { b: 2, c: 3 }
const [first, ...others2] = [1, 2, 3];
console.log(others2); // [2, 3]
rest 속성은 패턴에서 마지막에 와야 하며 뒤에 쉼표가 와서는 안 됩니다.
const [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma
// Always consider using rest operator as the last element
다른 JavaScript 문법과 마찬가지로 암시적 깊은 복사(deep-copy)는 없습니다. rest 속성은 새 객체(또는 배열)를 만들지만, 그 멤버 값들이 객체 참조라면 그 참조는 그대로 유지됩니다.
예제 (Examples)
배열 구조 분해 (Array destructuring)
기본 변수 할당
const foo = ["one", "two", "three"];
const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
소스보다 더 많은 요소로 구조 분해하기
할당문 오른쪽에 길이 N인 배열을 구조 분해할 때, 왼쪽에 지정한 변수 개수가 N보다 많으면 처음 N개의 변수만 값을 받습니다. 나머지 변수의 값은 undefined가 됩니다.
const foo = ["one", "two"];
const [red, yellow, green, blue] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // undefined
console.log(blue); // undefined
변수 값 교환 (Swapping variables)
두 변수의 값을 하나의 구조 분해 표현식으로 교환할 수 있습니다. 구조 분해 없이 두 값을 교환하려면 임시 변수(또는 일부 저수준 언어에서는 XOR-swap 기법)가 필요합니다.
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
const arr = [1, 2, 3];
[arr[2], arr[1]] = [arr[1], arr[2]];
console.log(arr); // [1, 3, 2]
함수에서 반환된 배열 파싱하기
함수에서 배열을 반환하는 것은 항상 가능했습니다. 구조 분해를 사용하면 배열 반환값을 더 간결하게 다룰 수 있습니다. 아래 예에서 f()는 [1, 2]를 반환하며, 이를 한 줄로 구조 분해합니다.
function f() {
return [1, 2];
}
const [a, b] = f();
console.log(a); // 1
console.log(b); // 2
반환값의 일부 무시하기
관심 없는 반환값은 무시할 수 있습니다:
function f() {
return [1, 2, 3];
}
const [a, , b] = f();
console.log(a); // 1
console.log(b); // 3
const [c] = f();
console.log(c); // 1
모든 반환값을 무시할 수도 있습니다:
[, ,] = f();
다만 이 경우에는 구조 분해를 쓰지 않고 함수를 호출하는 편이 더 명확할 것입니다. 반환값을 사용할 필요는 없습니다.
rest 속성으로 바인딩 패턴 사용하기
배열 구조 분해의 rest 속성은 또 다른 배열 또는 객체 바인딩 패턴일 수 있습니다. 내부 구조 분해는 rest 요소를 모은 뒤 생성된 배열에서 분해하기 때문에, 원래 이터러블에 있던 속성에는 이 방식으로 접근할 수 없습니다.
const [a, b, ...{ length }] = [1, 2, 3];
console.log(a, b, length); // 1 2 1
const [a, b, ...[c, d]] = [1, 2, 3, 4];
console.log(a, b, c, d); // 1 2 3 4
각 rest 속성이 항상 목록의 마지막이라면, 이러한 바인딩 패턴은 중첩될 수도 있습니다.
const [a, b, ...[c, d, ...[e, f]]] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c, d, e, f); // 1 2 3 4 5 6
반면 객체 구조 분해에서는 rest 속성으로 식별자만 올 수 있습니다.
const { a, ...{ b } } = { a: 1, b: 2 };
// SyntaxError: `...` must be followed by an identifier in declaration contexts
let a, b;
({ a, ...{ b } } = { a: 1, b: 2 });
// SyntaxError: `...` must be followed by an assignable reference in assignment contexts
정규식 매치 결과 풀어내기
정규식의 exec() 메서드가 매치를 찾으면, 문자열에서 매치된 전체 부분을 첫 요소로, 그다음 각 괄호 그룹에 매치된 부분들을 담은 배열을 반환합니다. 구조 분해를 사용하면 이 배열에서 각 부분을 쉽게 풀어낼 수 있으며, 전체 매치가 불필요하면 무시할 수도 있습니다.
function parseProtocol(url) {
const parsedURL = /^(\w+):\/\/([^/]+)\/(.*)$/.exec(url);
if (!parsedURL) {
return false;
}
console.log(parsedURL);
// ["https://developer.mozilla.org/en-US/docs/Web/JavaScript",
// "https", "developer.mozilla.org", "en-US/docs/Web/JavaScript"]
const [, protocol, fullHost, fullPath] = parsedURL;
return protocol;
}
console.log(
parseProtocol("https://developer.mozilla.org/en-US/docs/Web/JavaScript"),
);
// "https"
어떤 이터러블에든 배열 구조 분해 사용하기
배열 구조 분해는 오른쪽 피연산자의 이터러블 프로토콜(iterable protocol)을 호출합니다. 따라서 배열이 아니어도 어떤 이터러블이든 구조 분해할 수 있습니다.
const [a, b] = new Map([
[1, 2],
[3, 4],
]);
console.log(a, b); // [1, 2] [3, 4]
이터러블이 아닌 것은 배열로 구조 분해할 수 없습니다.
const obj = { 0: "a", 1: "b", length: 2 };
const [a, b] = obj;
// TypeError: obj is not iterable
이터러블은 모든 바인딩이 할당될 때까지만 순회됩니다.
const obj = {
*[Symbol.iterator]() {
for (const v of [0, 1, 2, 3]) {
console.log(v);
yield v;
}
},
};
const [a, b] = obj; // Only logs 0 and 1
rest 바인딩은 지연 없이 즉시 평가되어 이전 이터러블을 쓰는 대신 새 배열을 만듭니다.
const obj = {
*[Symbol.iterator]() {
for (const v of [0, 1, 2, 3]) {
console.log(v);
yield v;
}
},
};
const [a, b, ...rest] = obj; // Logs 0 1 2 3
console.log(rest); // [2, 3] (an array)
객체 구조 분해 (Object destructuring)
기본 할당
const user = {
id: 42,
isVerified: true,
};
const { id, isVerified } = user;
console.log(id); // 42
console.log(isVerified); // true
새 변수 이름으로 할당하기
객체 속성을 풀어내 객체 속성과 다른 이름의 변수에 할당할 수 있습니다.
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;
console.log(foo); // 42
console.log(bar); // true
여기서 const { p: foo } = o는 객체 o에서 이름이 p인 속성을 가져와 foo라는 지역 변수에 할당합니다.
새 변수 이름으로 할당하고 기본값 제공하기
한 속성은 다음 두 가지를 동시에 할 수 있습니다:
- 객체에서 풀려 다른 이름의 변수에 할당되기
- 풀려난 값이
undefined일 경우에 대비해 기본값이 지정되기
const { a: aa = 10, b: bb = 5 } = { a: 3 };
console.log(aa); // 3
console.log(bb); // 5
함수 매개변수로 전달된 객체에서 속성 풀어내기
함수 매개변수로 전달된 객체도 변수로 풀 수 있으며, 함수 본문 안에서 접근할 수 있습니다. 객체 할당과 마찬가지로 구조 분해 문법은 새 변수가 원래 속성과 같은 이름이거나 다른 이름을 가질 수 있고, 원래 객체가 그 속성을 정의하지 않을 경우를 위한 기본값을 지정할 수 있습니다.
예를 들어 사용자 정보를 담은 다음 객체를 생각해 봅시다.
const user = {
id: 42,
displayName: "jdoe",
fullName: {
firstName: "Jane",
lastName: "Doe",
},
};
전달된 객체의 속성을 같은 이름의 변수로 풀어내는 방법을 보여줍니다. 매개변수 값 { id }는 함수에 전달된 객체의 id 속성을 같은 이름의 변수로 풀어내 함수 안에서 사용하라는 뜻입니다.
function userId({ id }) {
return id;
}
console.log(userId(user)); // 42
풀어낼 변수의 이름을 정의할 수도 있습니다. 여기서는 displayName 속성을 풀어 함수 본문에서 사용할 dname으로 이름을 바꿉니다.
function userDisplayName({ displayName: dname }) {
return dname;
}
console.log(userDisplayName(user)); // "jdoe"
중첩 객체도 풀어낼 수 있습니다. 아래 예는 fullName.firstName 속성을 name이라는 변수로 풀어냅니다.
function whois({ displayName, fullName: { firstName: name } }) {
return `${displayName} is ${name}`;
}
console.log(whois(user)); // "jdoe is Jane"
함수 매개변수의 기본값 설정하기
=를 사용해 기본값을 지정할 수 있으며, 전달된 객체에 지정된 속성이 없으면 이 값이 변수 값으로 사용됩니다. 아래는 기본 크기가 'big', 기본 좌표가 x: 0, y: 0, 기본 반지름이 25인 함수입니다.
function drawChart({
size = "big",
coords = { x: 0, y: 0 },
radius = 25,
} = {}) {
console.log(size, coords, radius);
// do some chart drawing
}
drawChart({
coords: { x: 18, y: 30 },
radius: 30,
});
위 drawChart 함수 시그니처에서 구조 분해된 왼쪽은 빈 객체 = {}를 기본값으로 가집니다. 이 기본값 없이 함수를 작성할 수도 있습니다. 다만 기본값을 생략하면 함수는 호출 시 인수가 최소 하나 제공되기를 기대합니다. 반면 현재 형태로는 drawChart()를 인수 없이 호출할 수 있습니다. 그렇지 않다면 최소한 빈 객체 리터럴을 제공해야 합니다.
자세한 내용은 "기본 매개변수 > 기본값 할당이 있는 구조 분해 매개변수" 문서를 참고하세요.
중첩 객체와 배열 구조 분해
const metadata = {
title: "Scratchpad",
translations: [
{
locale: "de",
localizationTags: [],
lastEdit: "2014-04-14T08:43:37",
url: "/de/docs/Tools/Scratchpad",
title: "JavaScript-Umgebung",
},
],
url: "/en-US/docs/Tools/Scratchpad",
};
const {
title: englishTitle, // rename
translations: [
{
title: localeTitle, // rename
},
],
} = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"
for...of 반복과 구조 분해
const 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 (const {
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"
계산된 객체 속성 이름과 구조 분해
객체 리터럴에서처럼 계산된 속성 이름을 구조 분해와 함께 사용할 수 있습니다.
const key = "z";
const { [key]: foo } = { z: "bar" };
console.log(foo); // "bar"
유효하지 않은 JavaScript 식별자인 속성 이름
유효한 JavaScript 식별자가 아닌 속성 이름도, 유효한 대체 식별자를 제공하면 구조 분해할 수 있습니다.
const foo = { "fizz-buzz": true };
const { "fizz-buzz": fizzBuzz } = foo;
console.log(fizzBuzz); // true
원시값 구조 분해 (Destructuring primitive values)
객체 구조 분해는 속성 접근과 거의 동등합니다. 즉, 원시(primitive) 값을 구조 분해하려 하면 그 값은 대응하는 래퍼(wrapper) 객체로 감싸지고, 그 래퍼 객체에서 속성에 접근합니다.
const { a, toFixed } = 1;
console.log(a, toFixed); // undefined ƒ toFixed() { [native code] }
속성 접근과 마찬가지로, null이나 undefined를 구조 분해하면 TypeError가 발생합니다.
const { a } = undefined; // TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined.
const { b } = null; // TypeError: Cannot destructure property 'b' of 'null' as it is null.
패턴이 비어 있어도 이는 발생합니다.
const {} = null; // TypeError: Cannot destructure 'null' as it is null.
배열과 객체 구조 분해 결합하기
배열과 객체 구조 분해는 함께 결합할 수 있습니다. 아래 props 배열의 세 번째 요소를 가져온 다음 그 객체의 name 속성을 얻고 싶다면 다음처럼 하면 됩니다:
const props = [
{ id: 1, name: "Fizz" },
{ id: 2, name: "Buzz" },
{ id: 3, name: "FizzBuzz" },
];
const [, , { name }] = props;
console.log(name); // "FizzBuzz"
객체를 분해할 때 프로토타입 체인을 탐색합니다
객체를 분해할 때 어떤 속성이 객체 자체에 없으면 프로토타입 체인을 따라 계속 탐색합니다.
const obj = {
self: "123",
__proto__: {
prot: "456",
},
};
const { self, prot } = obj;
console.log(self); // "123"
console.log(prot); // "456"
명세 (Specifications)
구조 분해 할당은 ECMAScript 언어 명세에 정의되어 있으며, 최신 명세를 기준으로 구현되어 있습니다.
브라우저 호환성 (Browser compatibility)
이 기능은 대부분의 현대 브라우저에서 널리 지원됩니다(Baseline Widely available). 자세한 호환성 표는 MDN의 브라우저 호환성 섹션을 참고하세요.
더 알아보기
- 할당 연산자 (Assignment operators)
- 구조 분해 기초 (MDN)
- ES6 in Depth: Destructuring (hacks.mozilla.org, 2015)
- 관련 연산자:
new,this,typeof,instanceof,void