10일차에는 자바스크립트 핸드북 파트5,6에 배운내용을 정리했다.
객체형은 원시형과 달리 다양한 데이터를 담을 수 있다. 키로 구분된 데이터 집합이나 복잡한 개체(entity)를 저장할 수 있다. 객체는 자바스크립트 거의 모든 면에 녹아있는 개념이므로 자바스크립트를 잘 다루려면 객체를 잘 이해하고 있어야 한다.
객체는 중괄호 {…}를 이용해 만들 수 있다. 중괄호 안에는 ‘키(key): 값(value)’ 쌍으로 구성된 프로퍼티(property) 를 여러 개 넣을 수 있는데, 키엔 문자형, 값엔 모든 자료형을 넣을 수 있다. 프로퍼티 키는 ‘프로퍼티 이름’ 이라고도 부른다.
객체를 선언하는 방법에는 다음 2가지 방식이 있다.
// 문법
let user = new Object(); // '객체 생성자' 문법
let user = {}; // '객체 리터럴' 문법
// 예시
let user = { // 객체
name: "John", // 키: "name", 값: "John"
age: 30 // 키: "age", 값: 30
};
// 프로퍼티 값 얻기
alert( user.name ); // John
alert( user.age ); // 30
delete 연산자를 사용해 프로퍼티를 삭제할 수도 있다.
delete user.age;
여러 단어를 조합해 만들 때는 따옴표를 넣어줘야 한다.
let user = {
name: "John",
age: 30,
"likes birds": true // 복수의 단어는 따옴표로 묶어야 합니다.
};
// 값을 불러올 때는 대괄호[]표기법을 사용한다.
let user = {};
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
마지막 프로퍼티는 쉼표(trailing)로 끝낼 수 있다.
let user = {
name: "John",
age: 30,
}
프로퍼티 값 단축 구문(property value shorthand)을 사용할 수 있다.
function makeUser(name, age) {
return {
name, // name: name 과 같음
age, // age: age 와 같음
// ...
};
}
in 연산자를 통한 프로퍼티 존재여부를 확인할 수 있다.
// 문법
"key" in object
// 예시
let user = { name: "John", age: 30 };
alert( "age" in user ); // user.age가 존재하므로 true가 출력됩니다.
alert( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false가 출력됩니다.
객체도 반복문을 통해 갑
// 문법
for (key in object) {
// 각 프로퍼티 키(key)를 이용하여 본문(body)을 실행합니다.
}
// 예시
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// 키
alert( key ); // name, age, isAdmin
// 키에 해당하는 값
alert( user[key] ); // John, 30, true
}
변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소’인 객체에 대한 '참조 값’이 저장된다.
객체는 메모리 내 어딘가에 저장되고, 변수 user엔 객체를 '참조’할 수 있는 값이 저장된다.
따라서 객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않는다.
let user = { name: "John" };
let admin = user; // 참조값을 복사함
참조에 의한 비교
객체 비교 시 동등 연산자 ==와 일치 연산자 ===는 동일하게 동작한다.
비교 시 피연산자인 두 객체가 동일한 객체인 경우에 참을 반환한다.
let a = {};
let b = a; // 참조에 의한 복사
alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true
객체의 '진짜 복사본’을 만들려면 '얕은 복사(shallow copy)'를 가능하게 해주는 Object.assign이나 '깊은 복사’를 가능하게 해주는 _.cloneDeep(obj)를 사용하면 됩니다. 이때 얕은 복사본은 중첩 객체를 처리하지 못한다.
// 문법
Object.assign(dest, [src1, src2, src3...])
// 예시
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// permissions1과 permissions2의 프로퍼티를 user로 복사합니다.
Object.assign(user, permissions1, permissions2);
// now user = { name: "John", canView: true, canEdit: true }
object.assign에 동작 방식은 다음과 같다.
객체 프로퍼티에 할당된 함수를 메서드(method)라고 부른다. 객체를 사용하여 개체를 표현하는 방식을 객체 지향 프로그래밍(object-oriented programming, OOP) 이라 부른다.
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
alert("안녕하세요!");
};
user.sayHi(); // 안녕하세요!
다음과 같이 메서드 내부에서 this 키워드를 사용하면 객체에 접근할 수 있다.
let user = {
name: "John",
age: 30,
sayHi() {
// 'this'는 '현재 객체'를 나타냅니다.
alert(this.name);
}
};
user.sayHi(); // John
this는 객체가 없어도 호출이 가능하다.
function sayHi() {
alert(this);
}
sayHi(); // undefined
자바스크립트에서 this는 런타임에 결정된다. 메서드가 어디서 정의되었는지에 상관없이 this는 ‘점 앞의’ 객체가 무엇인가에 따라 ‘자유롭게’ 결정된다.
화살표 함수를 사용할 때는 this 고유한 값을 가지지 않고 외부함수에서 this를 참조한다.
let user = {
firstName: "보라",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // 보라
객체 리터럴 {...} 을 사용하면 객체를 쉽게 만들 수 있다. 개발을 하다보면 복수의 사용자, 메뉴 내 다양한 아이템을 객체로 표현하려고 하는 경우나 유사한 객체를 여러개 만들어야 할 때가 있다.
그럴때 'new' 연산자와 생성자 함수를 사용하면 유사한 객체 여러 개를 쉽게 만들 수 있다.
재사용할 수 있는 객체 생성 코드를 구현하는 일이 생성자 함수를 쓰는 이유다.
생성자 함수를 만들때는 두 가지 규칙이 있다.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("보라");
alert(user.name); // 보라
alert(user.isAdmin); // false
익명 생성자 함수는 재사용할 필요가 없는 복잡한 개체를 만들 때 사용할 수 있다. 익명 생성자 함수를 사용하면 재사용은 막으면서 코드는 캡슐화 할 수 있다.
let user = new function() {
this.name = "John";
this.isAdmin = false;
// 사용자 객체를 만들기 위한 여러 코드.
// 지역 변수, 복잡한 로직, 구문 등의
// 다양한 코드가 여기에 들어갑니다.
};
생성자 함수는 메서드 끼리 더해주는 것도 가능하다.
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "제 이름은 " + this.name + "입니다." );
};
}
let bora = new User("이보라");
bora.sayHi(); // 제 이름은 이보라입니다.
/*
bora = {
name: "이보라",
sayHi: function() { ... }
}
*/
생성자 함수의 특징은 다음과 같다.
객체엔 map, filter 같은 배열 전용 메서드를 사용할 수 없다.
하지만 Object.entries와 Object.fromEntries를 순차적으로 적용하면 객체에도 배열 전용 메서드 사용할 수 있다.
사용방법은 다음과 같다.
1. Object.entries(obj)를 사용해 객체의 키-값 쌍이 요소인 배열을 얻는다.
2. 1.에서 만든 배열에 map 등의 배열 전용 메서드를 적용한다.
3. 2.에서 반환된 배열에 Object.fromEntries(array)를 적용해 배열을 다시 객체로 되돌린다.
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
let doublePrices = Object.fromEntries(
// 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다.
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
alert(doublePrices.meat); // 8
개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기기도 하고 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 있다.
이럴 때 객체나 배열을 변수로 '분해’할 수 있게 해주는 특별한 문법인 구조 분해 할당(destructuring assignment) 을 사용하면 된다. 이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring)를 사용하면 된다.
배열 분해하기
// 이름과 성을 요소로 가진 배열
let arr = ["Bora", "Lee"]
// 구조 분해 할당을 이용해
// firstName엔 arr[0]을
// surname엔 arr[1]을 할당하였습니다.
let [firstName, surname] = arr;
alert(firstName); // Bora
alert(surname); // Lee
쉼표를 사용하여 필요없는 배열의 요소도 버릴 수 있다.
// 두 번째 요소는 필요하지 않음
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
아래 예시와 같이 객체를 분해해서 목표 변수에 저장할 수 있다.
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
// 함수 호출한 값을 기본값으로 설정할 수도 있다.
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
나머지 패턴(...)을 사용해서 변수에 분해한 값을 할당할 수도 있다.
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = 이름이 title인 프로퍼티
// rest = 나머지 프로퍼티들
let {title, ...rest} = options;
// title엔 "Menu", rest엔 {height: 200, width: 100}이 할당됩니다.
alert(rest.height); // 200
alert(rest.width); // 100
중첩 구조 분해(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
extra(할당 연산자 좌측의 패턴에는 없음)를 제외한 options 객체의 모든 프로퍼티가 상응하는 변수에 할당되어있다.
변수 width, height, item1, item2엔 원하는 값이, title엔 기본값이 저장되어있다.
그런데 전용 변수 대신 size와 items 안의 정보를 변수에 할당하였기 때문에 위 예시에서 size와 items 전용 변수는 없다는 점에 유의해야 한다.
구조분해는 매개변수 모두를 객체에 모아 함수가 전달받은 객체를 분해하여 변수에 할당하고 원하는 작업을 수행할 수 있다.
// 함수에 전달할 객체
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"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width는 w에,
height: h = 200, // height는 h에,
items: [item1, item2] // items의 첫 번째 요소는 item1에, 두 번째 요소는 item2에 할당함
}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
10일차 공부를 마치며...
객체에 구조분해 문법을 익혀봤는데, 다른 코드를 참고할 때 구조분해에 문법을 잘 몰라서 의아했었는데 배우고 나니까 전에 코드도 잘 보이고 활용도 할 수 있을 것 같아서 유익했다.