Destructuing Assignment
About
JavaScript에는 구조 분해 할당(Destructuing Assignment)이라는 편리한 문법이 있다. 굉장히 많이 쓰이는 문법인데 한 번 알아보자.
내용은 전부 MDN 문서에서 가져온 것이지만 실제 개발하면서 겪은 예시도 중간 중간 넣어보았다.
문법 (Syntax)
개요
=
를 기준으로 왼쪽에 할당받을 변수를, 오른쪽에 분해할 대상을 적어서 순서대로 대입할 수 있다.
const [a, b] = [10, 20]
console.log(a) // 10
console.log(b) // 20
a
에는 10
, b에는 20
이 담기게 된다.
const x = [1, 2, 3, 4, 5]
const [y, z] = x
console.log(y) // 1
console.log(z) // 2
배열 및 객체
const obj = { a: 1, b: { c: 2 } }
const { a, b: { c: d} } = obj
// Two variables are bound: `a`, `d`
const obj = { a: 1, b: { c: 2 } }
const { a } = obj // `a`는 상수(constant)
let { b: { c: d } } = obj // `d`는 재할당이 가능하다
const
로 할 경우 내부 변수는 read-only이지만,let
으로 할 경우 내부 변수는 재할당이 가능하다.
const numbers = []
const obj = { a: 1, b: 2 }
({ a: numbers[0], b: numbers[1] } = obj)
// `a`, `b`는 `numbers`의 프로퍼티로 들어간다
안되는 경우도 있다.
const numbers = []
const obj = { a: 1, b: 2 }
const { a: numbers[0], b: numbers[1] } = obj
// 위 코드는 다음과 같다:
// const numbers[0] = obj.a
// const numbers[1] = obj.b
// 따라서 틀리다.
디폴트 값
각 구조 분해된 프로퍼티는 디폴트 값을 가질 수 있다.
프로퍼티가 존재하지 않거나
undefined
이면 디폴트 값이 사용된다.프로퍼티가
null
값을 가지면 디폴트 값이 적용되지 않는다.
const [a = 1] = [] // a is 1
const { b = 2 } = { b: undefined } // b is 2
const { c = 2 } = { c: null } // c is null
디폴트 값은 표현식(expression)이 될 수도 있다. 필요할 때만 evaluated 된다.
const { b = console.log('hey') } = { b: 2 }
// `b`가 우측에 존재하므로 디폴트 값을 evaluate 하지 않기 때문에 console.log() 되지 않는다.
Rest Property
구조 분해 패턴을 ...rest
와 같이 rest property로 끝낼 수도 있다. 이 패턴으로 객체나 배열의 남은 프로퍼티를 새로운 객체나 배열에 할당할 수 있다.
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 property는 마지막에 있어야 하며, trailing comma (
,
)를 가지면 안된다.
다른 문법에서의 구조 분해 패턴
변수를 바인딩하는 다른 많은 문법에서 구조 분해 패턴을 사용할 수 있다. 예를 들자면:
for...in
,for...of
루프의 루프 변수함수의 매개변수
catch
바인딩 변수
예시 (Examples)
배열 구조 분해
기본적인 배열 할당
const foo = ['one', 'two', 'three']
const [red, yellow, green] = foo
console.log(red) // "one"
console.log(yellow) // "two"
console.log(green) // "three"
소스보다 많은 원소 구조 분해
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)
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]
함수로부터 반환된 배열 파싱하기
함수로부터 반환된 배열 값을 좀더 간결하게 접근할 수 있다.
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
바인딩 패턴을 Rest Property로 사용하기
배열의 구조 분해 할당에서의 rest property는 또 다른 배열이나 객체의 바인딩 패턴이 될 수 있다. 이 방법으로 동시에 프로퍼티나 배열의 인덱스를 분리(unpack)할 수 있다.
const [a, b, ...{ pop, push }] = [1, 2]
console.log(a, b) // 1 2
console.log(pop, push) // [Function pop] [Function push]
const [a, b, ...[c, d]] = [1, 2, 3, 4]
console.log(a, b, c, d) // 1, 2, 3, 4
이런 바인딩 패턴은 각 rest property가 마지막에 위치한 한 중첩이 가능하다.
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
Warning: 하지만 객체의 구조 분해는 identifier만이 rest property로 사용 가능하다.
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
정규 표현식으로 분리(unpacking)하기
정규표현식의 exec()
메소드가 매치를 찾으면 첫 번째 요소로 문자열에서 매치된 부분 전체 (full match)를, 그리고 나머지 요소로 ()
로 그룹화된 것에 매치되는 문자열의 부분들을 갖는 배열을 반환한다.
구조 분해 할당으로 full match를 무시하고 나머지 부분을 분리할 수 있다.
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이든 배열 구조 분해 사용하기
const [a, b] = new Map([[1, 2], [3, 4]])
console.log(a, b) // [1, 2] [3, 4]
배열 구조 분해는 right-hand side에서 iterable 프로토콜을 사용하기 때문에 어떤 iterable이든 구조 분해가 가능하다.
객체 구조 분해
기본 할당
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
p
를foo
로,q
를bar
로 받는다.
새로운 변수 이름을 할당하고 디폴트 값 설정하기
프로퍼티에 대해서 다음 두 가지 작업을 (동시에) 할 수 있다.
객체로부터 분리(unpack)해 새로운 이름의 변수로 할당하기
분리된(unpacked) 값이
undefined
일 경우에 대비해 디폴트 값 설정하기
const { a: aa = 10, b: bb = 5 } = { a: 3 }
console.log(aa) // 3
console.log(bb) // 5
함수 매개변수로 전달된 객체로부터 프로퍼티 분리하기(Unpacking)
const user = {
id: 42,
displayName: 'jdoe',
fullName: {
firstName: 'John',
lastName: 'Doe',
},
};
// `user`에서 `id`만 분리할 수 있다.
function userId({ id }) {
return id
}
console.log(userId(user)) // 42
// 중첩된 프로퍼티도 가능하다.
function whois({ displayName, fullName: { firstName: name } }) {
return `${displayName} is ${name}`
}
console.log(whois(user)) // "jdoe is John"
함수 매개변수의 디폴트 값 설정하기
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,
})
중첩된 객체와 배열 구조 분해
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
반복과 구조 분해
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"
계산된(Computed) 객체 프로퍼티 이름과 구조 분해
const key = 'z'
const { [key]: foo } = { z: 'bar' }
console.log(foo) // "bar"
올바르지 않은 JavaScript 식별자명을 프로퍼티 이름으로 사용하기
const foo = { 'fizz-buzz': true }
const { 'fizz-buzz': fizzBuzz } = foo
console.log(fizzBuzz) // true
배열과 객체가 합쳐진 형태의 구조 분해
배열과 객체 구조 분해를 동시에 할 수 있다.
const props = [
{ id: 1, name: 'Fizz'},
{ id: 2, name: 'Buzz'},
{ id: 3, name: 'FizzBuzz'}
]
const [,, { name }] = props
console.log(name) // "FizzBuzz"
객체가 구조 분해될 때 프로토타입 체인(Prototype chain)를 따라가 찾아간다
객체를 구조 분해할 때, 프로퍼티가 해당 프로퍼티 안에 없다면(not accessed in itself) 프로토타입 체인을 따라서 계속해서 찾아갈 것(look up)이다.
const obj = {
self: '123',
__proto__: {
prot: '456',
},
}
const { self, prot } = obj
// self "123"
// prot "456" (Access to the prototype chain)
결론 (Conclusion)
정리하고 보니 쓰임새가 새삼 참 많다. (정리하다가 몇 번 졸았다. 🥱)
근데 보면 알겠지만 Examples 섹션에 있는 내용이 반 이상이다. 예시가 다양해서 많아 보이는 것이지, 사용법과 원리만 알면 자유롭게 사용할 수 있다.
제일 중요한 건 경험을 통해 구조 분해를 사용하면 적합한지를 체득하고 적재적소에 사용하는 것이다.
REF
Last updated
Was this helpful?