객체와 배열은 자바스크립트에서 가장 많이 쓰이는 자료 구조이다.
키를 가진 데이터 여러 개를 하나의 엔티티에 저장할 땐 객체를, 컬렉션에 데이터를 순서대로 저장할 땐 배열을 사용한다. (배열에는 순서가 있고, 객체에는 순서(index)가 없기 때문!)
개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기곤 한다. 가끔은 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 생기기도 한다.
이럴 때 객체나 배열을 변수로 '분해’할 수 있게 해주는 특별한 문법인 구조 분해 할당(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(' ');
예시)
// 이름과 성을 요소로 가진 배열
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"] --> string에도 적용 가능!
let [one, two, three] = new Set([1, 2, 3]);
할당 연산자 좌측엔 다양한 형태가 올 수 있다.
할당 연산자 좌측엔 ‘할당할 수 있는(assignables)’ 것이라면 어떤 것이든 올 수 있다
아래와 같이 객체 프로퍼티도 가능하다!
let user = {};
[user.name, user.surname] = "Bora Lee".split(" ");
console.log(user); // { name: 'Bora', surname: 'Lee' } 이렇게 object도 구성 가능!
배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때가 있다. 이럴 때는 점 세 개 ...를 붙인 매개변수 하나를 추가하면 ‘나머지(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 함수를 할당한 아래 예시를 보면 알 수 있다.
값이 제공되지 않았을 때만 함수가 호출되므로, 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 {...} 안의 순서가 바뀌어도 동일하게 동작함
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
할당 연산자 좌측엔 좀 더 복잡한 패턴이 올 수도 있다. 분해하려는 객체의 프로퍼티와 변수의 연결을 원하는 대로 조정할 수도 있다.
객체 프로퍼티를 프로퍼티 키와 다른 이름을 가진 변수에 저장해볼 수 있다. options.width를 w라는 변수에 저장하는 식으로 말이다.
좌측 패턴에 콜론(:)을 사용하면 원하는 목표를 달성할 수 있다. 즉 객체 프로퍼티의 이름으로가 아니라 다른 이름으로 바꾸는 것이 가능하다는 것이다.
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
console.log(height); // --> 이건 오류가 난다. height를 h로 바꿨기 때문!
콜론은 '분해하려는 객체의 프로퍼티: 목표 변수’와 같은 형태로 사용한다. 위 예시에선 프로퍼티 width를 변수 w에, 프로퍼티 height를 변수 h에 저장했다. 프로퍼티 title은 동일한 이름을 가진 변수 title에 저장된다.
프로퍼티가 없는 경우를 대비하여 =을 사용해 기본값을 설정하는 것도 가능하다.
let options = {
title: "Menu"
};
let {width = 100, height = 200, title} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
배열 혹은 함수의 매개변수에서 했던 것처럼 객체에도 표현식이나 함수 호출을 기본값으로 할당할 수 있다. 표현식이나 함수는 값이 제공되지 않았을 때 평가 혹은 실행되게 된다.
아래 예시를 실행하면 width 값만 물어보고 title 값은 물어보지 않는다.
let options = {
title: "Menu"
};
let {width = prompt("width?"), title = prompt("title?")} = options;
alert(title); // Menu
alert(width); // prompt 창에 입력한 값
콜론과 할당 연산자를 동시에 사용할 수도 있다.
let options = {
title: "Menu"
};
let {width: w = 100, height: h = 200, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
프로퍼티가 많은 복잡한 객체에서 원하는 정보만 뽑아오는 것도 가능하다.
let options = {
title: "Menu",
width: 100,
height: 200
};
// title만 변수로 뽑아내기
let { title } = options;
alert(title); // Menu
객체나 배열이 다른 객체나 배열을 포함하는 경우, 좀 더 복잡한 패턴을 사용하면 중첩 배열이나 객체의 정보를 추출할 수 있다. 이를 중첩 구조 분해(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
console.log(size, items) // --> size와 items에는 구조분해할당된 것이 없다.
// 실수하기 좋으니 반드시 기억할 것!
extra(할당 연산자 좌측의 패턴에는 없음)를 제외한 options 객체의 모든 프로퍼티가 상응하는 변수에 할당됐다.

변수 width, height, item1, item2엔 원하는 값이, title엔 기본값이 저장되었다.
그런데 위 예시에서 size와 items에 구조분해할당 된 변수는 없다는 점에 유의해야한다. (실수하기 좋음) 전용 변수 대신 우리는 size와 items 안의 정보를 변수에 할당한 것이다.
자바스크립트에서 화면(screen)으로부터 DOM 엘리먼트의 위치(position)를 알 수 있는 방법이 존재한다. getBoundingClientRect() 메서드는 DOMRect요소의 크기와 브라우저 뷰포트에 상대적인 위치에 대한 정보를 제공 하는 객체를 반환하게된다.
getBoundingClientRect는 화면에서 잡은 dom을 어디에 위치한 지 알기 위해 사용한다.
현재 화면, 윈도우(window)룰 기준으로 특정 엘리먼트의 위치 값을 구하는 방법이 존재한다. 아래 스크린샷 처럼 가능하다. 상단과 하단의 어두운 부분은 스크롤을 이동하여 화면에서 보이지 않는 영역이다.

즉 현재 브라우저 화면을 기준으로 위치 값을 가져온다는 개념이다. 만약 값을 구할 엘리먼트가 감춰져 안 보인다면? 이 경우 화면 밖에 위치하므로 당연히 마이너스 값이 나타나게 될 것이다. 이런 이유로 문서 전체인 Document 기준이라면 항상 0보다 큰 값을 가지겠지만 화면을 스크롤함에 따라서 마이너스 값이 나올 수 있는 것이다.
이 전에는 브라우저 호환성 문제로 엘리먼트의 정확한 위치 값을 얻기가 매우 어려웠다고 한다. 때로는 부모 요소의 부모 요소를 계속 값을 계속 더하는 반복적인 작업을 진행하기도 했다고 한다.
그러나 현재, DOM 메소드 getBoundingClientRect()를 사용하면 매우 간단하게 원하는 요소의 위치 값을 얻을 수 있다.
간단한 예시
DOM.getBoundingClientRect()
이 방법은 대부분의 브라우저를 지원하면서 가장 쉽고 빠르게 위치 값을 가져올 수 있다.
아래에는 ID값 test를 가지는 이미지가 있다.
<img id="test" src="my_img.jpg" />
이 이미지가 화면으로 어디에 위치해 있는지 알기 위해서 getBoundingClientRect()를 적용해본다.
const ele = document.querySelector('#test');
const imgRect = ele.getBoundingClientRect();
이제 위치 값을 구하기 위해 아래처럼 콘솔창에 출력해보면 다음과 같은 결과가 나온다.
console(imgRect);
// 출력결과
{
bottom: 178
height: 44
left: 212.5
right: 1092.5
top: 134
width: 880
x: 212.5
y: 134
}
getBoundingClientRect의 return 값은 해당 dom의 width, height, left, top, right, bottom, x, y를 알려주며 width와 height는 뷰포트의 왼쪽 상단을 기준으로 한다.
top or y // 화면 상단 부터 대상의 처음 위치 값
bottom // 화면 상단 부터 대상의 끝 위치 값
left or x // 화면 좌측 부터 대상의 처음 위치 값
right // 화면 좌측 부터 대상의 끝 위치 값
width // 대상의 길이
height // 대상의 높이
이와 같이 간단하게 대상 엘리먼트의 위치 정보를 얻을 수 있다.

(대상의 width, height 값과 right, bottom 값도 출력함)

<img id="myImage" src="https://images.pexels.com/photos/16415740/pexels-photo-16415740.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1">
<script>
window.onload = () => {
var myImage = document.getElementById("myImage");
var rect = myImage.getBoundingClientRect();
console.log(rect.width);
console.log(rect.height);
}
</script>

getBoundingClientRect()를 사용하여 뷰포트 내의 위치뿐만이 아니라, 이미지의 가로 크기와 세로 크기를 추출할 수도 있다. 먼저 id로 이미지 요소를 가져온 후 getBoundingClientRect() 메소드를 호출한다. 여기서 리턴된 객체는 픽셀 단위의 이미지 가로, 세로 크기를 포함하고 있다.
훌륭한 글이네요. 감사합니다.