객체
- JavaScript의 8가지 자료형 중 하나이다.
나머지 7개는 하나의 타입에 실제값 하나만 담을 수 있는 원시형(primitive type)으로 불린다. - 객체타입은 다양한 데이터를 담을 수 있다.
- 중괄호({})를 이용해서 생성한다.
key: value 쌍으로 구성된 프로퍼티(property)로 구성된다.
key: 문자형
value: 모든 자료형 - 프로퍼티 이름에는 예약어도 올 수 있지만, "__proto__"라는 문자열은 올 수 없다.
- 프로퍼티는 특이한 방식으로 정렬된다.
- 정수 프로퍼티일 경우 오름차순으로 정렬
- 그 외의 프로퍼티 추가한 순서대로 저장
- 정수와 문자가 같이 섞여있으면 정수 먼저 정렬 후 들어온 순서대 문자 프로퍼티를 정렬
정수 프로퍼티
- 프로퍼티 명이 정수인 프로퍼티
- 정수에서 다른형태로 왔다갔다 할 수 있는 문자열
ex)
Math.trunc(Number("49")) => 49 => 정수 프로퍼티 o
Math.trunc(Number("+49")) => 49 => 정수 프로퍼티 x
Math.trunc(Number("49.2")) => 49 => 정수 프로퍼티 x
// 두 가지 방법으로 생성 가능
let user = new Object();
let user = {};
// 중괄호를 이용해 객체를 선언하는 것을 '객체 리터럴' 이라고 한다.
let user = {
name: "KSH",
age: 20,
email: "aaaa@gmail.com",
"go home": true,
};
// 프로퍼티 마지막은 쉼표로 끝날 수 있음(trailing comma)
// 복수의 단어는 따옴표로 묶어야 함
// name, age, email => 프로퍼티 식별자 or 프로퍼티 이름
// dot notation을 이용해 프로퍼티 값 읽기 가능
console.log(user.name); // KSH
console.log(user.age); // 20
// 프로퍼티 추가 가능
user.isCalled = false;
// 프로퍼티 삭제 가능
delete user.email;
// const로 선언된 객체도 값 수정 가능
const member = {
name: "철수",
age: 5
}
member.name = "짱구";
console.log(member.name);
// member 자체는 고정되지만 내용물이 고정되지는 않음
// square bracket notation을 이용해 프로퍼티 값 읽기 가능
console.log(user["go home"]);
// []로는 변수를 키로 활용할 수 있지만, .는 불가능
let key = "name";
console.log(name.key); // undefined
console.log(name[key]); // KSH
computed property(계산된 프로퍼티)
- 객체 생성 시 리터럴 안의 프로퍼티 키가 대괄호로 둘러싸여 있는 것
// 계산된 프로퍼티를 사용하지 않을 경우
let fruit = prompt("과일 이름?". "apple");
let basket = {};
basket[fruit] = 2000;
// 계산된 프로퍼티를 사용할 경우
let fruit = prompt("과일 이름?". "apple");
let basket = {
[fruit] = 2000;
}
// 복잡한 표현식도 대입 가능
let fruit = "Apple";
let basket = {
["computed " + fruit]: 2000; // basket.computedApple
}
shorthand property(단축 프로퍼티)
- 프로퍼티 이름과 값이 변수(or 파라미터)와 동일할 때 사용
function createUser(name, age) {
return {
name: name,
age: age,
};
}
// 아래의 코드로 변형 가능
function createUser(name, age) {
return {
name,
age
};
}
let user = createUser("KSH", 20);
console.log(user.name, user.age); // KSH 20
프로퍼티 확인하기
in 연산자
// JavaScript는 존재하지 않는 프로퍼티에 접근 시 undefined를 반환. 에러 x
let user = {};
console.log(user.property === undefined); // true
// in 키워드로 존재여부 확인하기
// 이 때에는 프로퍼티명을 따옴표로 감싸야함
let user = {
name: "KSH",
age: 20
}
console.log("name" in user); // true
console.log("no" in user); // false
// for...in 구문으로 프로퍼티 순회하기
let user = {
name: "KSH",
age: 25,
email: "aaaa@gmail.com",
isAdmin: false
};
for(let key in user) {
console.log(key, user[key]);
}
//name KSH
//age 25
//email aaaa@gmail.com
//isAdmin false
객체 복사
- 원시형은 값 그대로 저장/복사되는 반면, 객체는 참조에 의해(by reference) 저장/복사된다.
- 변수에 객체가 저장될 시 객체가 저장되어있는 메모리 주소(객체에 대한 참조값)가 저장된다.
let test01 = {a:1, b:2};
// 객체는 메모리 내에 저장되고, test01에는 객체를 참조할 수 있는 값이 저장된다.
// => 객체가 할당된 변수를 복사하면 참조값이 복사된다. 객체는 복사 안 됨.
let test02 = test01;
test02.c = 3;
console.log(test01.c); //3
// test01, test02 모두 같은 객체를 참조해서 test02를 수정했지만 test01이 바뀐다.
let a = {};
let b = a;
console.log(a == b); // true
console.log(a === b); // true
let c = {};
let d = {};
console.log(c == d); // false
// 값은 같을 수 있지만 c,d 모두 독립된 객체
객체 복제하기
- 객체를 복사하면 동일한 객체에 대한 참조 값이 하나 더 만들어 지는 것
- 똑같은 객체를 복제하려면,
- 새로운 객체를 만들어서 기존 객체의 프로퍼티를 원시수준까지 복사
- Object.assign 사용
// 1번
let test01 = {a:1, b:2};
let clone = {};
for(let key in test01) {
clone[key] = test01[key];
}
// 2번
Object.assign(dest, [src1, src2, ...])
// dest: 복제해서 넣을 곳
// src: 복사하고자 하는 객체
// ex)
let user = {name: "KSH"};
let setAge = {age: 10};
let setEmail = {email: "aaaa@gmail.com"};
Object.assign(user, setAge, setEmail);
for(let key in user) {
console.log(key, user[key]);
}
//name KSH
//age 10
//email aaaa@gmail.com
// 복제 시 이미 존재하는 프로퍼티가 있다면 덮어쓰기됨
메서드 & this
메서드
- 객체가 특정 '행동'을 할 수 있는 능력
=> 함수 표현식으로 메서드를 만들고 객체 프로퍼티에 할당해준다.
let user = {name: "KSH", age: 25};
user.sayHello = function() {
console.log("hello~");
}
user.sayHello(); // hello~
// 이미 선언된 함수도 할당 가능
function sayHi() {
console.log("Hi~~");
}
user.sayHi = sayHi;
user.sayHi(); // Hi~~
메서드 단축
test01 = {
testFunc : function() {
console.log("test01~");
}
}
test02 = {
testFunc() {
console.log("test02~~");
}
}
test01.testFunc(); // test01~
test02.testFunc(); // test02~~
// function()을 생략해도 메서드 정의가 가능하다
// 객체 상속과 관련한 미묘한 차이가 존재한다고 함..
this
- 대부분의 메서드는 객체 프로퍼티의 값을 활용
=> 메서드는 객체에 저장된 정보에 접근할 수 있어야 함
let user = {
name: "KSH",
age: 25,
sayName() { console.log(this.name); },
sayAge() { console.log(this.age); }
};
user.sayName(); // KSH
user.sayAge(); // 25
- this 키워드를 통해 메서드 내부의 객체에 접근 가능
(정확히는 메서드를 호출할 때 사용된 객체를 나타냄)
⚠️
this대신 user.sayName()처럼 외부 변수를 이용해 객체를 참조할 수도 있지만
user가 member라는 변수에 복사된 후 user가 다른 값으로 수정됐다고 가정했을 때
user.sayName()은 KSH가 아닌 다른 값을 참조하게 된다.
let user = { name: "KSH", age: 25, sayName01() { console.log(user.name); }, sayName02() { console.log(this.name); } }; let member = user; user = null; member.sayName02(); // KSH member.sayName01(); // error
this 특징
- JavaScript의 this는 모든 메서드에서 사용 가능
- this의 값은 런타임 때 결정된다(컨텍스트에 따라 달라진다).
- 장점: 메서드를 하나만 만들어서 여러객체에서 재사용 가능
- 단점: 유연하게 사용할 수 있는 만큼 실수가 많이 발생함
- 동일한 함수를 호출해도 다른 객체라면 this가 참조하는 값이 달라진다.
- this는 .앞의 객체에 따라 '자유롭게' 결정된다.
- this의 값은 런타임 때 결정된다(컨텍스트에 따라 달라진다).
function hello() {
console.log(this.name);
}
// 에러가 발생하지 않음
let user = {name: "짱구"};
let member = {name: "철수"};
function hello() {
console.log(this.name);
}
user.func = hello;
member.func = hello;
user.func(); // 짱구
user["func"](); // 짱구
member.func(); // 철수
member["func"](); // 철수
- 메서드가 정의된 객체를 참조하는 this => bound this <=> 자유로운 this
화살표 함수는 this 없음
- 화살표 함수는 일반 메서드와는 다르게 고유한 this가 없다.
- 화살표 함수에서 this를 참조하면 외부함수의 this값을 가져온다.
=> 별도로 this를 생성하지 않고, 외부 컨텍스트의 this를 이용하려면 화살표 함수가 유용하다.
let user = {
name: "KSH",
sayName() {
let arrow = () => console.log(this.name);
arrow();
}
}
user.sayName(); // KSH
생성자 & new
생성자 함수
- new 연산자를 통해 유사 객체를 여러개 찍어낼 수 있는 함수
- 생성자도 일반 함수이지만 첫 글자는 대문자!
function User(name, age) {
this.name = name;
this.age = age;
this.isAdmin = false;
}
let user01 = new User("짱구", 15);
let user02 = new User("철수", 25);
console.log(user01.name); // 짱구
console.log(user01.age); // 15
console.log(user01.isAdmin); // false
console.log(user02.name); // 철수
console.log(user02.age); // 25
console.log(user02.isAdmin); // false
// 생성자 호출 시 동작 흐름
// 1. 빈 객체를 만들어 this에 할당한다.
this = {};
// 2. 함수 본문을 실행한다.
// 2-1. this에 새로운 프로퍼티를 추가해준다.
this.name = name;
this.age = age;
this.isAdmin = false;
// 3. this를 반환한다.
return this;
- 파라미터가 없는 생성자 함수는 괄호 생략 가능
// 똑같이 동작
let user = new User();
let user = new User;
생성자의 return
- 생성자 함수에는 보통 return문이 없다.
반환해야할 내용은 모두 this에 저장되고, this는 자동 반환되기 때문.- 만약 생성자 함수에 return이 사용되면,
- 객체타입를 return할 경우, this 대신 객체가 반환된다.
- 원시타입을 return할 경우, return이 무시된다.
- 만약 생성자 함수에 return이 사용되면,
function BigAnimal() {
this.name = "호랑이";
return {name: "사자"}; // 객체 반환
}
console.log(new BigAnimal().name); // 사자
function SmallAnimal() {
this.name = "토끼";
return "햄스터"; // 객체 이외의 타입 반환
}
console.log(new SmallAnimal().name); // 햄스터가 출력될 것 같지만 토끼 출력
생성자의 메서드
- 생성자 메서드를 사용하면 파라미터에 따라 다른 결과를 낼 수 있는 유연성이 확보된다.
function Player(name) {
this.name = name;
this.playGame = () => {
console.log(this.name + " 경기 시작하겠습니다.");
};
}
let player = new Player("철수");
player.playGame(); // 철수 경기 시작하겠습니다.
객체 순회
- Object.keys(obj) - 객체의 키만 담은 배열 반환
- Object.values(obj) - 객체의 값만 담은 배열 반환
- Object.entries(obj) - key: value 쌍을 담은 배열 반환
Map, Set, Array | 일반 객체 | |
문법 | xxx.keys(); | Object.keys(obj); (파라미터 필수. obj.keys() 아님) |
return 값 | iterable 객체 | 진짜 배열 |
- 문법이 차이나는 이유
- 유연성
data라는 객체가 자체적으로 data.values() 라는 메서드를 구현해서 사용할 때, 커스텀 메서드를 구현했더라도 Object.values(data)와 같은 내장 메서드를 사용할 수 있게 된다. - Object.* 호출 시 iterable 객체가 아닌 배열을 반환함.
- 유연성
객체 변환
- 일반 객체는 map, filter 등의 배열 전용 메서드가 없다.
=> Object.entries와 Object.fromEntries를 순차적으로 사용해 같은 효과를 낼 수 있다.
let basket = {banana: 1, apple:2, grape: 3};
let doublePrices = Object.fromEntries(
Object.entries(prices).map((k, v) => [k, v*2])
);
console.log(basket); //{ banana: 1, apple: 2, grape: 3 }
console.log(doublePrices); //{ banana: 2, apple: 4, grape: 6 }
구조 분해 할당(destructing assignment)
- 객체나 배열을 변수로 분해해주는 문법
let arr = [1,2,3];
// 구조 분해 할당 => 인덱스 없이도 배열 요소 사용가능
let [one, two, three] = arr;
console.log(one, two, three); // 1 2 3
//split()같은 return값이 배열인 메서드 사용
let [one, two] = "1 2".split(" ");
console.log(one, two); // 1 2
//쉼표를 잘 사용해서 중간요소 무시 가능
let [first, second, , last] = ["흰둥이", "짱구", "짱아", "철수"];
console.log(first, second, last); // 흰둥이 짱구 철수
// '='의 오른쪽에는 모든 iterable 객체 사용 가능
let [a,b,c] = "ABC"; // A B C
let [one, two, three] = new Set([1,2,3]); // 1 2 3
// 프로퍼티에도 할당 가능
let user = {};
[user.name, user.age] = ["KSH", 25];
//.entries()와 구조 분해를 조합해 키,값 분해 할당 가능
let user = {
name: "KSH",
age: 25
}
for(let [k, v] of Object.entries(user)) {
console.log(`${k}: ${v}`); // name: KSH age: 25
}
// 변수 간 값 쉽게 교체 가능
let admin = "짱구";
let user = "철수";
[user, admin] = [admin, user];
console.log(admin, user); // 철수 짱구
//...를 통해 나머지 요소 가져오기
let [a,b,...c] = [1,2,3,4,5,6];
console.log(a,b); // 1 2
// 나머지 요소는 새로운 배열로 저장
console.log(c[0], c[1]) // 3 4
- 분해하려는 배열의 요소보다 할당하려는 변수의 개수가 많아도 에러가 발생하지 않음
할당할 값이 없으면 undefined로 취급됨
let [text1, text2] = [];
console.log(text1, text2); // undefined undefined
- '='를 통해 할당값이 없을 때의 기본값을 지정해줄 수 있음
let [name = "Anonymous", nickname = "Ghost"] = ["짱구"];
console.log(name + " is " + nickname); // 짱구 is Ghost
// 표현식이나 함수호출도 기본값이 될 수 있다.
function setNickname() {
return "Ghost";
}
let [username, nickname = setNickname()] = ["KSH"];
console.log(username + " is " + nickname); // KSH is Ghost
객체 분해
let settings = {
auto: false;
username: "KSH",
volume: "MAX"
}
let {auto, username, volume} = settings;
// 순서 바껴도 상관없음
// 원하는 이름으로 저장 가능
let {auto: a, volume: v, username} = settings;
console.log(a, v, username); // false MAX KSH
// {분해하려는 객체의 프로퍼티: 목표 변수, ...}
// 기본값 설정 가능
let user = {
username: "KSH",
age: 25
};
let {username: un = "Unknown", age, isAdmin = false} = user;
console.log(un, age, isAdmin); // KSH 25 false
// 원하는 값만 뽑아오기
let options = {
title: "Menu",
subtitle: "juice"
}
let {title} = options;
console.log(title); // Menu
// rest pattern(...)으로 나머지 할당하기
let basket = {
banana: 1000,
apple: 2000,
grape: 3500
}
let {banana, ...rest} = basket;
console.log(rest.apple, rest["grape"]); // 2000 3500
let 없이 사용하기
// 잘못된 코드
let title, width, height;
{title, width, height} = {title:"Menu", width:100, height:100};
// JavaScript는 표현식 안에 있지 않으면서
// 주요 코드 흐름 상에 있는 중괄호({...})는 코드 블록으로 인식한다.
// 코드 블록은 statement를 묶기 위해 사용되는 것
// 소괄호로 감싸주면 에러가 해결된다.
let title, width, height;
({title, width, height} = {title:"Menu", width:100, height:100});
console.log(title); // Menu
중첩 구조 분해(nested destructing)
- 객체나 배열이 다른 객체나 배열을 포함할 때 객체의 정보를 추출하는 것
let games = {
title: {
game1: 10000,
game2: 15000
},
users: ["짱구", "철수"]
};
let {
title: {
game1,
game2
},
users: [user1, user2]
}
= games;
console.log(game1, game2, user1, user2); // 10000 15000 짱구 철수
// title과 users 자체가 가지는 변수는 없고
// 그 안의 정보들이 변수로 할당된다.
파라미터를 구조분해로 선택적으로 받기
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
// ...
}
// 위 코드는 넘겨지는 인수의 순서가 틀릴 경우 문제가 발생할 수 있다.
// 기본값이 설정된 파라미터들은 굳이 인수를 넘겨주지 않아도 된다.
// 기본값을 사용해도 괜찮은 경우 아래와 같이 undefined를 여러개 넘겨야한다.
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
// 구조 분해를 통해 가독성 높이기
// 함수에 전달할 객체 생성
let options = {
title: "Menu",
items: ["Item1", "Item2"]
};
// 스마트한 메서드는 전달받은 객체를 분해해서 변수에 할당한다.
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
console.log(title, height, width); // Menu 100 200
console.log(items); // [ 'Item1', 'Item2' ]
}
showMenu(options);
// 메서드의 파라미터를 구조 분해할 땐 인수가 반드시 전달된다고 가정해야 한다.
// 모든 파라미터에 기본값을 할당하려면 빈 객체를 넘겨야 한다.
showMenu({}); // 이렇게 아니면
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) { // 이렇게
참고
https://ko.javascript.info/destructuring-assignment
구조 분해 할당
ko.javascript.info
https://ko.javascript.info/object#ref-83
객체
ko.javascript.info
https://ko.javascript.info/object-copy#ref-1065
참조에 의한 객체 복사
ko.javascript.info
https://ko.javascript.info/object-methods#ref-228
메서드와 this
ko.javascript.info
https://ko.javascript.info/constructor-new#ref-1196
new 연산자와 생성자 함수
ko.javascript.info
https://ko.javascript.info/keys-values-entries#ref-400
Object.keys, values, entries
ko.javascript.info
'📜JavaScript' 카테고리의 다른 글
JavaScript 찍먹하기 - (4) 함수 (1) | 2023.04.27 |
---|---|
JavaScript 찍먹하기 - (3) 배열 (0) | 2023.04.26 |
JavaScript 찍먹하기 - (2) 조건문, 반복문 (0) | 2023.04.26 |
JavaScript 찍먹하기 - (1) 변수, 자료형, 연산자 등 (0) | 2023.04.24 |