이전 글에서 자료형에 대해 다룬 적이 있다. 총 8가지의 자료형 중 7개는 오직 하나의 데이터(문자열, 숫자 등)만 담을 수 있어 '원시형(primitive type)'이라 부른다.
반면, 객체형은 원시형과 달리 다양한 데이터를 담을 수 있다!
키로 구분된 데이터 집합이나 복잡한 개체(entity)를 저장할 수 있다. 객체는 자바스크립트 거의 모든 면에 녹아있는 개념이므로 자바스크립트를 잘 다루려면 객체를 잘 이해하고 있어야 한다.
객체는 중괄호 {...}
를 이용해 만들 수 있다. 중괄호 안에는
'키(key) : 값(value)' 쌍으로 구성된 프로퍼티(property)
를 여러 개 넣을 수 있는데, 키는 문자형, 값엔 모든 자료형이 허용된다. 프로퍼티 키는 '프로퍼티 이름'이라고도 부른다.
빈 객체를 만드는 방법은 두 가지가 있다.
// 1. 객체 생성자 문법
let user = new Object();
// 2. 객체 리터럴 문법
let user = {};
두 번째 방법인 객체 리터럴은 중괄호를 이용해 객체를 선언하는 방법으로, 객체를 선언할 땐 주로 이 방법을 사용한다.
중괄호 {...} 안에는 키:값
쌍으로 구성된 프로퍼티가 들어간다.
let user = {
name : "John", // 키: "name", 값: "John"
age : 30 // 키: "age", 값: 30
};
객체 user에는 프로퍼티가 2개 있다.
개발자는 프로퍼티를 추가, 삭제할 수 있다.
💡 점 표기법(dot notation)을 이용하면 프로퍼티 값을 읽는 것도 가능하다.
프로퍼티 값에는 모든 자료형이 올 수 있다.
delete
연산자를 사용하면 프로퍼티를 삭제할 수도 있다.
delete user.age;
여러 단어를 조합해 프로퍼티 이름을 만든 경우, 프로퍼티 이름을 따옴표로 묶어줘야 한다.
let user = {
name: "John",
age: 30,
"likes birds": true // 복수의 단어는 따옴표로 묶어야 한다
};
마지막 프로퍼티 끝은 쉼표로 끝날 수 있다.
let user = {
name: "John",
age: 30, // 마지막 프로퍼티가 쉼표로 끝남
};
이런 쉼표를 'trailing(길게 늘어지는)' 혹은 'hanging(매달리는)' 쉼표라고 부른다. 이렇게 끝에 쉼표를 붙이면 모든 프로퍼티가 유사한 형태를 보이기 때문에 프로퍼티를 추가, 삭제, 이동하는 게 쉬워진다.
💡 상수 객체는 수정될 수 있다.
EX.
![]()
const
는user
의 값을 고정하지만 그 내용은 고정하지 않는다.
const
는user=...
를 전체적으로 설정하려고 할 때만 오류가 발생한다.
여러 단어를 조합해 프로퍼티 키를 만든 경우엔, 점 표기법을 사용해 프로퍼티 값을 읽을 수 없다.
// 문법 에러 발생!
user.likes birds = true;
자바스크립트는 위와 같은 코드를 이해하지 못한다. user.likes까지 이해하다가 예상치 못한 birds를 만나면 문법 에러가 발생한다.
'점'은 '유효한 변수 식별자'인 경우에만 사용할 수 있다.
위에서 언급한 '유효한 변수 식별자'에는
키가 유효한 변수 식별자가 아닌 경우에는 점 표기법 대신 '대괄호 표기법(square bracket notation)'이라 불리는 방법을 쓸 수 있다. 대괄호 표기법은 키에 어떤 문자열이 있건 상관없이 동작한다.
let user = {};
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
대괄호 표기법 안에서 문자열을 사용할 땐 문자열을 따옴표(종류 상관 X)로 묶어줘야 한다.
대괄호 표기법을 사용하면 아래 예시에서 변수를 키로 사용한 것과 같이 문자열 뿐만 아니라 모든 표현식의 평가 결과를 프로퍼티 키로 사용할 수 있다.
let key = "likes birds";
// user["likes birds"] = true; 와 같습니다.
user[key] = true;
변수 key는 런타임에 평가되기 때문에 사용자 입력값 변경 등에 따라 값이 변경될 수 있다. 어떤 경우든, 평가가 끝난 이후의 결과가 프로퍼티 키로 사용된다.
EX.
let user = {
name: "John",
age: 30
};
let key = prompt("사용자의 어떤 정보를 얻고 싶으신가요?", "name");
// 변수로 접근
alert( user[key] ); // John (프롬프트 창에 "name"을 입력한 경우)
❗) 이 때, 점 표기법은 이런 방식이 불가능하다.
let user = {
name: "John",
age: 30
};
let key = "name";
alert( user.key ) // undefined
객체를 만들 때 객체 리터럴 안의 프로퍼티 키가 대괄호로 둘러싸여 있는 경우, 이를 계산된 프로퍼티(computed property)라 부른다.
EX.
let fruit = prompt("어떤 과일을 구매하시겠습니까?", "apple");
let bag = {
[fruit]: 5, // 변수 fruit에서 프로퍼티 이름을 동적으로 받아온다.
};
alert( bag.apple ); // fruit에 "apple"이 할당되었다면, 5가 출력.
위 예시에서 [fruit]
는 프로퍼티 이름을 변수 fruit
에서 가져오겠다는 것을 의미한다.
사용자가 프롬프트 대화상자에 apple을 입력했다면 bag
엔 {apple : 5}
가 할당되었을 것이다.
아래 예시는 위 예시와 동일하게 동작한다.
let fruit = prompt("어떤 과일을 구매하시겠습니까?", "apple");
let bag = {};
// 변수 fruit을 사용해 프로퍼티 이름을 만들었다.
bag[fruit] = 5;
두 방식 중 계산된 프로퍼티를 사용한 아래 예시가 더 깔끔해보인다.
한편, 다음 예시처럼 대괄호 안에는 복잡한 표현식이 올 수도 있다.
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
대괄호 표기법은 프로퍼티 이름과 값의 제약을 없애주기 때문에 점 표기법보다 훨씬 강력하다만, 작성하기 번거롭다는 단점도 있다.
- 프로퍼티 이름이 확정된 상황이고, 단순한 이름 → 처음엔 점 표기법을 사용
- 뭔가 복잡한 상황이 발생했을 때 → 대괄호 표기법으로 변경
실무에서는 프로퍼티 값을 기존 변수에서 받아와 사용하는 경우가 종종 있다.
EX.
function makeUser(name, age) {
return {
name: name,
age: age,
// ...등등
};
}
let user = makeUser("John", 30);
alert(user.name); // John
이렇게 변수를 사용해 프로퍼티를 만드는 경우는 아주 흔한데,
프로퍼티 값 단축 구문(property value shorthand)을 사용하면 코드를 짧게 줄일 수 있다.
name:name
대신 name
만 적어줘도 프로퍼티를 설정할 수 있다.
function makeUser(name, age) {
return {
name, // name: name 과 같음
age, // age: age 와 같음
// ...
};
}
한 객체에서 일반 프로퍼티와 단축 프로퍼티를 함께 사용하는 것도 가능하다.
let user = {
name, // name: name 과 같음
age: 30
};
이전 글에서 언급한 바와 같이, 변수 이름(키)에는 예약어(for, let, return 등)를 사용하면 안된다.
그런데, 객체 프로퍼티엔 이런 제약이 없다.
// 예약어를 키로 사용해도 괜찮다.
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
어떤 문자형, 심볼형 값도 프로퍼티 키가 될 수 있다. (식별자로 쓰이는 심볼형은 여기서 확인 - 추후 링크 달 예정)
문자형이나 심볼형에 속하지 않은 값은, 문자열로 자동 형 변환된다.
EX. 키에 숫자 0을 넣으면 문자열 "0"으로 자동변환된다.
let obj = {
0: "test" // "0": "test"와 동일
};
// 숫자 0은 문자열 "0"으로 변환되기 때문에 두 얼럿 창은 같은 프로퍼티에 접근
alert( obj["0"] ); // test
alert( obj[0] ); // test (동일한 프로퍼티)
이와 같이 객체 프로퍼티 키에 쓸 수 있는 문자열엔 제약이 없지만, 역사적인 이유 때문에 특별 대우를 받는 이름이 하나 있다.
바로 __proto__
let obj = {};
obj.__proto__ = 5; // 숫자를 할당합니다.
alert(obj.__proto__); // [object Object] - 숫자를 할당했지만 값은 객체가 되었다. 의도한대로 동작하지 않는다.
결과)
원시값 5를 할당했는데 무시되었다.
proto의 본질은 프로토타입 상속에서, 이 문제를 어떻게 해결할 수 있을지에 대해선 프로토타입 메서드와 proto가 없는 객체에서 자세히 다룰 예정이다.
자바스크립트 객체의 중요한 특징 중 하나는 다른 언어와 달리, 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined를 반환한다는 것이다.
이런 특징을 응용하면 프로퍼티 존재 여부를 쉽게 확인할 수 있다.
let user = {};
alert( user.noSuchProperty === undefined ); // true는 '프로퍼티가 존재하지 않음'을 의미한다.
이렇게 undefined와 비교하는 것 이외에도 연산자
in
을 사용하면 프로퍼티 존재 여부를 확인할 수 있다.
문법은 다음과 같다.
"key" in object
EX.
let user = { name: "John", age: 30 };
alert( "age" in user ); // user.age가 존재하므로 true가 출력된다.
alert( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false가 출력된다.
in
왼쪽엔 반드시 프로퍼티 이름이 와야한다. (프로퍼티 이름은 보통 따옴표로 감싼 문자열이다.)
대부분의 경우, 일치 연산자를 사용해서 프로퍼티 존재 여부를 알아내는 방법("=== undefined")은 꽤 잘 동작하는 편이다.
그런데 가끔은 이 방법이 실패할 때도 있다. 이럴 때 in
을 사용하면 프로퍼티 존재 여부를 제대로 판별할 수 있다.
프로퍼티는 존재하는데, 값에 undefined를 할당한 예시를 보자.
let obj = {
test: undefined
};
alert( obj.test ); // 값이 `undefined`이므로, 얼럿 창엔 undefined가 출력된다. 그런데 프로퍼티 test는 존재한다.
alert( "test" in obj ); // `in`을 사용하면 프로퍼티 유무를 제대로 확인할 수 있다(true가 출력됨).
obj.test는 실제 존재하는 프로퍼티다. 따라서 in 연산자는 정상적으로 true를 반환한다.
undefined는 변수는 정의되어 있으나 값이 할당되지 않은 경우에 쓰기 때문에 프로퍼티 값이 undefined인 경우는 흔치 않다. 값을 ‘알 수 없거나(unknown)’ 값이 ‘비어 있다는(empty)’ 것을 나타낼 때는 주로 null을 사용한다.
for..in
반복문을 사용하면 객체의 모든 키를 순회할 수 있다. for..in
은 이전에 다뤘던 for(;;)
반복문과는 완전히 다르다.
문법)
for (key in object) {
// 각 프로퍼티 키(key)를 이용하여 본문(body)을 실행합니다.
}
아래 예시를 실행하면 객체 user
의 모든 프로퍼티가 출력된다.
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// 키
alert( key ); // name, age, isAdmin
// 키에 해당하는 값
alert( user[key] ); // John, 30, true
}
for..in
반복문에서도 for(;;)
문처럼 반복 변수(looping variable)를 선언(let key
)했다.
🤷♂️ 객체의 프로퍼티에 순서가 있을까?
객체는 '특별한 방식으로 정렬'된다.
정수 프로퍼티(integer property)는 자동으로 정렬되고, 그 외의 프로퍼티는 객체에 추가한 순서 그대로 정렬된다. 예제를 살펴보며 정리하자.
아래 객체엔 국제전화 나라 번호가 담겨있다.
let codes = {
"49": "독일",
"41": "스위스",
"44": "영국",
// ..,
"1": "미국"
};
for (let code in codes) {
alert(code); // 1, 41, 44, 49
}
현재 개발 중인 애플리케이션의 주 사용자가 독일인이라고 가정해보자. 그럼 나라 번호를 선택하는 화면에서 49가 맨 앞에 오도록 하는 게 좋을 것 같다.
그런데 코드를 실행해 보면 예상과는 전혀 다른 결과가 출력된다.
이유는 나라 번호(키)가 정수이어서 1, 41, 44, 49 순으로 프로퍼티가 자동 정렬되었기 때문이다.
💡 정수 프로퍼티
정수에서 왔다 갔다 할 수 있는 문자열을 의미한다.
문자열 "49"는 정수로 변환하거나 변환한 정수를 다시 문자열로 바꿔도 변형이 없기 때문에 정수 프로퍼티다. 하지만 '+49'나 '1.2'와 같은 건 정수 프로퍼티가 아니다.
한편, 키가 정수가 아닌 경우엔 작성된 순서대로 프로퍼티가 나열된다. 예시를 살펴보자.
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // 프로퍼티를 하나 추가한다.
// 정수 프로퍼티가 아닌 프로퍼티는 추가된 순서대로 나열된다.
for (let prop in user) {
alert( prop ); // name, surname, age
}
결과)
예상과 같이 추가된 순서대로 출력된다.
위 국제번호 예시에서 49(독일 나라 번호)를 가장 위에 출력되도록 하려면 나라 번호가 정수로 취급되지 않도록 속임수를 쓰면 된다. 각 나라 번호 앞에 '+'
를 붙여보자.
let codes = {
"+49": "독일",
"+41": "스위스",
"+44": "영국",
// ..,
"+1": "미국"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
결과) 이처럼 정수인 프로퍼티를 원하는 대로 정렬하고 싶을 때 위와 같은 방법을 쓰면 된다.
이 글은 https://ko.javascript.info/ 를 참고하여 작성하였습니다.