객채와 배열은 자바스크립트에서 가장 많이 쓰이는 자료구조이다.
키를 가진 데이터 여러 개를 하나의 엔티티에 저장할 땐 객체를, 컬렉션에 데이터를 순서대로 저장할 떈 배열을 사용한다.
개발을 하다보면 함수에 객체나 배열을 전달해야 하는 경우가 생긴다.
가끔은 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 생기기도 한다.
이럴 떄 객체나 배열을 변수로 분해할 수 있게 해주는 특별한 문법인 구조 분해 할당을 사용할 수 있다.
이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring assignment)을 사용할 수 있다.
배열이 어떻게 변수로 분해되는지 예제를 통해 살펴보자
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 firstName = arr[0]'
let surname = arr[1];
쉼표를 사용하여 요소 무시하기
쉼표를 사용하면 필요하지 않은 배열 요소를 버릴 수 있다.
// 두 번쨰 요소는 필요하지 않음
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(title); // Consul
두 번째 요소는 생력되었지만, 세 번째 요소는 title이라는 변수에 할당된 것을 확인할 수 있다.
할당할 변수가 없기 떄문에 네 번째 요소 역시 생략되었다.
할당 연산자 우측엔 모든 이터러블이 올 수 있다.
배열뿐만 아니라 모든 이터러블(반복 가능한 객체)에 구조 분해 할당을 적용할 수 있다.
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
Objet.entries(obj) 메서드와 구조 분해를 조합하면 객체의 키와 값을 순회해 변수로 분해 할당할 수 있다.
let user = {
name: "John",
age: 30
};
// 객체의 키와 값 순회하기
for (let [key, vaule] of Objet.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, age: 30
}
두 변수에 저장된 값을 교환할 때 구조 분해 할당을 사용할 수 있다.
let guest = "Jane";
let admin = "Pete";
[guest, admin] = [adin,guest];
예시에서는 임시 배열을 만들어 두 변수를 담고, 요소 순서를 교체해 배열을 분해하는 방식을 사용했다.
이 방식을 사용하면 두 개뿐만 아니라 그 이상의 변수에 담긴 값도 교환할 수 있다.
배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때가 있다.
이럴 때는 점 세 개 ...을 붙인 매개변수 하느를 추가하면 나머지(rest)요소를 가져올 수 있다.
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(rest[0]); // Consul
alert(rest.length); // 2
rest는 나머지 배열 요소들이 저장된 새로운 배열이 된다.
rest대신에 다른 이름을 사용해도 되는데 변수 앞의 점 세 개와 변수가 가장 마지막에 위치해야한다는 점은 지켜야한다.
할당하고자 하는 변수의 개수가 분해하고자 하는 배열의 길이보다 크더라도 에러가 발생하지 않는다.
할닥할 값이 없으면 undefined로 취급되기 떄문이다.
let [firstName, surname] = [];
alert(firstName); //undefined
alert(surnae); // undefined
=을 이용하면 할당할 값이 없을 때 기본으로 할당해줄 값인 기본값을 설정해줄 수 있다.
// 기본값
let [name = "Guest", surname = "Anoymous"] = ["Julius];
alert(name); // Julius
alert(surname); // Anoymous
복잡한 표현식이나 함수 호출값도 기본값이 될 수 있다.
이렇게 기본식으로 표현식이나 함수를 설정하면 할당한 값이 없을 때 표현식이 평가되거나 함수가 호출된다.
기본값으로 두 개의 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 options = {
title: "Menu",
width: 100,
height: 200
}
// {객체 프로퍼티: 목표 변수}
let {width: w, height: h, title = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
콜론은 분해하려는 객체의 프로퍼티: 목표 변수와 같은형태로 사용한다.
위 예시에선 프로퍼티 width를 변수 w에, 프로퍼티 height를 변수 h에 저장했다.
프로퍼티 title은 동일한 이름을 가진 변수 titile에 저장된다.
프로퍼티가 없는 경우를 대비하여 = 을 사용해 기본값을 설정하는 것도 가능하다.
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 = prompot("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
분해하려는 객체의 프로퍼티 개수가 할당하련느 변수의 개수보다 많다면?
나머지를 어딘가에 할당하면 될 것이다.
나머지 패턴을 사오ㅛㅇ하면 배열에서 햇던 것처럼 나머지 프로퍼티를 어딘가에 할당하는 게 가능하다.
참고로 모던 브라우저는 나머지 패턴을 지원하지만 IE를 비롯한 몇몇 구식 브라우저는 나머지 패턴을 지원하지 않으므로 주의해서 사해야한다.
물론 바벨을 사용하면 된다.
나머지 패턴의 예시를 살펴보자.
let options = {
title: "Menu",
height: 200,
width: 100
}
// title = 이름이 tilte인 프로퍼티
// rest = 나머지 프로퍼티들
let {title, ...rest} = options;
// title엔 "Menu", rest엔 나머지 프로퍼티들이 할당된다.
alert(rest.hegith); 200
지금까지 할당연산 let {...} = {...} 안에서 변수를 선언하였다.
let 으로 새로운 변수를 선언하지 않고 기존에 있던 변수에 분해한 값을 할당할 수도 있는데, 이떄는 주의할 점이 있다.
잘못된 예시
let title, width, height;
{title,width,height} = {title: "Menu", width: 200, height:100};
위처럼 실행하면 SynstaxError: Unexpected token '='이라는 에러가 아랫줄에서 발생한다.
구조 분해 할당을 위해 사용한 {...}를 자바스크립트가 코드 블록으로 인식해서 에러가 발생하였다.
에러를 해결하려면 할당문을 괄호(...)로 감싸 자바스크립트가 {...}를 코드 블록이 아닌 표현식으로 해석하게 하면 된다.
let title, width, height;
// 에러가 발생하지 않는다.
({title, width, height} = {title: "Menu", width:200, height:100});
aler(title); // Menu
객체나 배열이 다른 객체나 배열을 포함하는 경우 좀 더 복잡한 패턴을 사용하면 중첩 배열이나 객체의 정보를 추출할 수 있다.
이를 중첩 분해라고 부른다.
아래 예시에서 객체 options의 size 프로퍼티 값은 또 다른 객체이다.
items프로퍼티는 배열을 값으로 가지고 있다.
대입 연산자 좌측의 패턴은 정보를 추출하려는 객체 options와 같은 구조를 갖추고 있다.
let options = {
size : {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// 코드를 여러 줄에 걸쳐 작성해 의도하는 바를 명확히 드러넴
let {
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
extra(할당 연산자 좌측의 패턴에는 없음)를 제외한 options객체의 모든 프로퍼티가 상응하는 변수에 할당되었다.
함수에 매개변수가 많은데 이중 상당수는 선택적으로 쓰이는 경우가 종종 있다.
사용자 인터페이스오 연관된 함수에서 이런 상황을 자주 볼 수 있다.
메뉴 생성에 관여하는 함수가 있을 떄, 메뉴에는 너비, 높이, 제목, 항목 리스트 등이 필요하기 떄문에
이 정보는 매개변수로 받는다.
리팩토링 전의 메뉴 생성 함수
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
// ...
}
위처럼 함수를 작성하면 넘겨주는 인수의 순서가 틀려 문제가 발생할 수 있다.
문서화가 잘 되어있다면 IDE가 순서를 틀리지 않게 도움을 주긴 하겠지만, 문제가 발생할 수 있다
이 외에도 대부분의 매개변수에 기본값이 설저되어 있어 굳이 인수를 넘겨주지 않아도 되는 경우에 문제가 발생한다.
매개변수가 많아질수록 가독성은 더 떨어진다.
// 기본값을 사용해도 괜찮은 경우 아래와 같아 undefined를 여러 개 넘겨줘야 한다.
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
위의 코드처럼 지저분해 보일 수 있다.
구조분해는 이럴 때 가독성을 높일 수 있다.
매개변수 모드를 객체에 모아 함수에 전달해, 함수가 전달받은 객체를 분해하여 변수에 할당하고
원하는 작업을 수행할 수 있도록 함수를 리팩토링 해보자
// 함수에 전달할 객체
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);
중첩 객체와 콜론을 조합하면 좀 더 복잡한 구조 분해도 가능하다.
let options = {
title: "My menu",
items: ["Item1","Item2"]
};
fnction showMenu({
title = "Untitled",
width: w = 100,
height: h = 200,
items: [item1, item2]
}) {
alert( `${title} ${w} ${h}` ) // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
또한 이렇게 함수 배개변수를 구조 분해할 땐 반드시 인수가 전달된다고 가정되고 사용된다.
showMenu(); // 인수가 전달되지 않기 때문에 에러가 발생할 수 있다.
showMenu({}); // 모든 인수에 기본값이 할단된다.
또한
function showMenu({title: "Menu", width = 100, height= 200} = `{}`) {
alert( `${title} ${wodtj} ${height}` );
}
showMenu(); // Menu 100 200
위처럼 인수 객체의 기본값을 빈 객체 {}로 설정하면 어떤 경우든 분해할 것이 새겨서 함수에 인수를 하나도 전달하지 않아도 에러가 발생하지 않는다.
let {prop: varName = default, ...rest} = objectlet [item1 = defualt, item2, ...rest] = arraylet user = {
name: "John",
years: 30
}
// 정답:
let {name, years:age, isAdmin = false} = user
가장 많은 급여를 받는 사람의 이름을 반환해 주는 함수 topSalary(salaries)를 만들어봅시다.
조건은 아래와 같다.
let salareis = {
"John": 100,
"Pete": 300,
"Mary": 250
}
const function topSalary(salaries) {
let max = 0;
let maxName = "";
for(let [name, salary] of Object.entries(salaries) ) {
if(max < salary) {
max = salary;
maxName = name;
}
}
return maxName;
}
날짜를 저장할 수 있고, 날짜와 관련된 메서드도 제공해주는 내장 객체 Date에 대해 알아보자
Date객체를 활용하면 생성 및 수정 시간을 저장하거나 시간을 측정할 수 있고, 현재 날짜를 출력하는 용도 등으로도 활용할 수 있다.
객체 생성하기
new Date() 를 호출하면 새로운 date객체가 만들어진다.
또한 인수 없이 호출하면 현재 날짜와 시간이 저장된 Date객체가 반환된다.
let now = new Date();
alert(now); // 현재 날짜 및 시간이 출력됨
new Date(milliseconds)
UTC기준 1970년 1월 1일 0시 0분 0초에서 millisceconds 밀리초 후의 시점이 저장된 Date 객체가 반환된다.
타임스탬프를 사용하면 날짜를 숫자 형태로 간편하게 나타낼 수 있다.
new Date(timesamp)를 사용해 타임스탬프를 사용해 특정 날짜가 저장된 Date객체를 손쉽게 만들 수 있고
date.getTime()메스드를 사용해 Date객체에서 타임스탬프를 추출하는 것도 가능하다.
1980년 1월 1일 이전 날짜에 해당하는 타임스탬프 값은 음수이다.
let Dec31_1969 = new Date(-24 * 3600 * 1000);
alert(Dec31_1969);
new Date(datestring)
인수가 하나인데 문자열이라면 해당 문자열은 자동으로 구문 분석된다.
구문 분석에 적용되는 알고리즘은 Date.parse에서 사용하는 알고리즘과 동일히ㅏ다.
let date = new Date("2017-01-26");
alert(date);
// 인수로 시간을 지정하지 않았기 때문에 GMT 자정이라고 가정
// 코드가 실행되는 시간대에 따라 출력 문자열이 바뀐다.
// Tuh Jan 26 2017 11:00:?00:00 GMT + 1100
// 혹은
// Wed Jan 25 2017 16:00:00 GMT - 0900 등이 출력된다.
Date객체의 메서드를 사용하면 연,월,일 등의 값을 얻을 수 있다.
복잡한 객체를 다루고 있다고 가졌했을때,
네트워크를 통해 객체를 어딘가에 보내거나 로깅 목적으로 객체를 출력해야한다면
객체를 문자열로 전환해야한다.
이때 전환된 문자열엔 원하는 정보가 있는 객체 프로퍼티가 모두 포함되어야 한다.
아래와 같은 메서드를 구현해 객체를 문자열로 전환해보자.
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${this.name}", age: ${this.age}}`
}
}
alert(user); // {name: "John", age: 30};
그런데 개발 과정에서 프로퍼티가 추가되거나 삭제 수정될 수 있다.
이렇게 되면 위에서 구현한 toString을 매번 수정해야 하는데 아주 고통스러운 작업이 될 것이다.
프로퍼티에 반복문을 돌리는 방법을 대안으로 사용할 수 있는데, 중첩 객체 등으로 인해 객체가 복잡한 경우
이를 문자열로 변경하는 건 까다로운 작업이라 이마저도 쉽지 않을 것이다.
다행히 자바스크립트엔 이런 문제를 해결해주는 방법이 있다.
관련 기능이 이미 구현되어 있어서 우리가 직접 코드를 짤 필요가 없다.
JSON(JavaScript Object Notation)은 값이나 객체를 나타내주는 범용 포맷으로 RFC 4627표준에 정의되어있다.
JSON은 본래 자바스크립트에서 사용할 목적으로 만들어진 포맷이다.
그런데 라이브러리를 사용하면 자바스크립트가 아닌 언어에서도 JSON을 충분히 다룰 수 있어서,
JSON을 데이터 교환 목적으로 사용하는 경우가 많다.
특히 클라이언트 측 언어가 자바스크립트일때 데이터 교환 목적으로 사용된다
서버 측 언어는 무엇이든 상관 없다.
자바스크립트가 제공하는 JSON관련 메서드는 아래와 같다.
객체 studuent에 JSON.stringify를 적용해보자.
let student = {
name: 'john',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
}
let json = JSON.stringify(student);
alert(typeof json); // string
alert(json);
/* JSON으로 인코딩된 객체:
{
"name" : "John",
"age" : 30,
"isAdmin" : false,
"courses" : ["html", "css", "js"],
"wife": null
}
*/
JSON.stringify(student)를 호출하자 student가 문자열로 바뀌었다.
이렇게 변경된 문자열은 JSON으로 인코딩된, 직렬화 처리된, 문자열로 반환된, 결젭된 객체라고 부른다.
객체는 이렇게 문자열로 변환 후에야 비로소 네트워크를 통해 전송하거나 저장소에 저장할 수 있다.
JSON으로 인코딩된 객체는 일반 객체와 다른 특징을 보인다.
JSON은 데이터 교환을 목적으로 만들어진 언어에 종속되지 않는 포맷이다
따라서 자바스크립트 특유의 객체 프로퍼티는 JSON.stringify가 처리할 수 없다.
JSON.stringify호출 시 무시되는 프로퍼티는 아래와 같다.
또한 JSON.stringify의 장점 중 하나는 중첩 객체도 알아서 문자열로 바꿔준다.
JSON.stringify의 전체 문법은 아래와 같다.
let json = JSON.stringify(value, [replacer,space])
인코딩 하려는 값
JSON으로 인코딩 하길 원하는 프로퍼티가 담긴 배열 또는 매핑 함수 function (key, vlaue)
서식 변경 목적으로 사용할 공백 문자 수