4.1 객체

42649 단어 JavaScriptJavaScript

자료형 챕터에서 배웠듯이 자바스크립트엔 여덟 가지 자료형이 있다. 이 중 일곱 개는 오직 하나의 데이터(문자열, 숫자 등)만 담을 수 있어 '원시형(primitive type)' 이라 부른다.

그런데 객체형은 원시형과 달리 다양한 데이터를 담을 수 있다. 키로 구분된 데이터 집합이나 복잡한 개체(entity)를 저장할 수 있다. 객체는 자바스크립트의 거의 모든 면에 녹아있는 개념으로 자바스크립트를 잘 다루려면 객체를 잘 이해하고 있어야 한다.

객체는 중괄호 {...} 를 이용해 만들 수 있다. 중괄호 안에는 '키(key):값(value)' 쌍으로 구성된 프로퍼티(property) 를 여러 개 넣을 수 있는데, 엔 문자형, 엔 모든 자료형이 허용된다. 프로퍼티 키는 '프로퍼티 이름' 이라고도 부른다.

서랍장을 상상하면 객체를 이해하기 쉽다. 서랍장 안 파일은 프로퍼티, 파일 각각에 붙어있는 이름표는 객체의 키라고 생각하면 된다. 복잡한 서랍장 안에서 이름표를 보고 원하는 파일을 쉽게 찾을 수 있듯이, 객채에선 키를 이용해 프로퍼티를 쉽게 찾을 수 있다. 추가나 삭제도 마찬가지이다.

빈 객체(빈 서랍장)를 만드는 방법은 두 가지가 있다.

let user = new Object(); // '객체 생성자' 문법
let user = {}; // '객체 리터럴' 문법

중괄호 {...} 를 이용해 객체를 선언하는 것을 객체 리터럴(object literal) 이라고 부른다. 객체를 선언할 땐 주로 이 방법을 사용한다.

리터럴과 프로퍼티

중괄호 {...} 안에는 '키: 값' 쌍으로 구성된 프로퍼티가 들어간다.

let user = {	//객체
  name: "John",	// 키: "name", 값: "John"
  age: 30		// 키: "age", 값: 30
};

'콜론(:)'을 기준으로 왼쪽엔 키가, 오른쪽엔 값이 위치한다. 프로퍼티 키는 프로퍼티 '이름' 혹은 '식별자' 라고도 부른다. 객체 user에는 프로퍼티가 두 개 있다.

  1. 첫 번째 프로퍼티 - "name"(이름)과 "John"(값)
  2. 두 번째 프로퍼티 - "age"(이름)과 30(값)

서랍장(객체 user) 안에 파일 두 개(프로퍼티 두 개) 가 담겼는데, 각 파일에 "name", "age"라는 이름표가 붙어있다고 생각하면 쉽다.

서랍장에 파일을 추가하고 ㅂ밸 수 있듯이 개발자는 프로퍼티를 추가, 삭제할 수 있다.

점 표기법(dot notation)을 이용하면 프로퍼티 값을 읽는 것도 가능하다.

// 프로퍼티 값 얻기
alert( user.name ); // John
alert( user.age ); // 30

프로퍼티 값엔 모든 자료형이 올 수 있다. 불린형 프로퍼티를 추가해보자.

user.isAdmin = true;

delete 연산자를 사용하면 프로퍼티를 삭제할 수 있다.

delete user.age;

여러 단어를 조합해 프로퍼티 이름을 만든 경우엔 프로퍼티 이름을 따옴표로 묶어줘야 한다.

let user = {
  name: "John",
  age: 30,
  "likes birds" : true // 복수의 단어는 따옴표로 묵어야한다.
};

마지막 프로퍼티 끝은 쉼표로 끝날 수 있다.

let user = {
  name: "John",
  age: 30,
}

이런 쉼표를 'trailing(길게 늘어지는)' 혹은 'hanging(매달리는)'쉼표라고 부른다. 이렇게 끝에 쉼표를 붙이면 모든 프로퍼티가 유사한 형태를 보이기 때문에 프로퍼티를 추가, 삭제, 이동하는 게 쉬워진다.

상수 객체는 수정될 수 있다. ✔

주의하자. const로 선언된 객체는 수정될 수 있다.

const user = {
  name: "John"
};

user.name = "Pete"; // (*)

alert(user.name); // Pete

(*)로 표시한 줄에서 오류를 일으키는 것처럼 보일 수 있지만 그렇지 않다. constuser의 값을 고정하지만, 그 내용은 고정하지 않는다.

constuser=...를 전체적으로 설정하려고 할 때만 오류가 발생한다.

대괄호 표기법

여러 단어를 조합해 프로퍼티 키를 만든 경우엔, 점 표기법을 사용해 프로퍼티 값을 읽을 수 없다.

// 문법 에러가 발생한다.
user.likes birds = ture;

자바스크립트는 위와 같은 코드를 이해하지 못한다. user.likes까지는 이해하다가 예상치 못한 birds를 만나면 문법 에러를 뱉어낸다.

'점’은 키가 '유효한 변수 식별자’인 경우에만 사용할 수 있다. 유효한 변수 식별자엔 공백이 없어야 한다. 또한 숫자로 시작하지 않아야 하며 $와 _를 제외한 특수 문자가 없어야 한다.

키가 유효한 변수 식별자가 아닌 경우엔 점 표기법 대신에 '대괄호 표기법(square bracket notation)'이라 불리는 방법을 사용할 수 있다. 대괄호 표기법은 키에 어떤 문자열이 있던지 상관없이 동작한다.

let user = {};

// set
user["likes birds"] = true;

// set 
alert(user["likes birds"]); // true

// delete
delete user["likes birds"];

이제 문법 에러가 발생하지 않는다. 대괄호 표기법 안에서 문자열을 사용할 땐 문자열을 따옴표로 묶어줘야 한다는 점에 주의하길 바란다.

대괄호 표기법을 사용하면 아래 예시에서 변수를 키로 사용한 것과 같이 문자열뿐만 아니라 모든 표현식의 평가 결과를 프로퍼티 키로 사용할 수 있다.

let key = "likes birds";

// user["likes birds"] = true; 와 같다
user[key] = true;

변수 key는 런타임에 평가되기 때문에 사용자 입력값 변경 등에 따라 값이 변경될 수 있다. 어떤 경우든, 평가가 끝난 이후의 결과가 프로퍼티 키로 사용된다. 이를 응용하면 코드를 유용하게 작성할 수 있다.

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) 라 부른다.

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
};

대괄호 표기법은 프로퍼티 이름과 값의 제약을 없애주기 때문에 점 표기법보다 훨씬 강력하다. 그런데 작성하기 번거롭다는 단점이 잇다.

단축 프로퍼티

실무에선 프로퍼티 값을 기존 변수에 받아와 사용하는 경우가 종종 있다.

function makeUser(name, age) {
  return {
    name: name,
    age: age,
    // .. 등등
  };
}

let user = makeUser("John", 30);
alert(user.name); // "John"

위 예시의 프로퍼티들은 이름과 값이 변수의 이름과 동일하다. 이렇게 변수를 사용해 프로퍼티를 만드는 경우는 아주 흔한데, 프로퍼티 값 단축 구문(property value shortamd) 을 사용하면 코드를 짧게 줄일 수 있다.

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.retur ); // 6

이와 같이 프로퍼티 이름엔 특별한 제약이 없다. 어떤 문자형, 심볼형 값도 프로퍼티 키가 될 수 있다.

문자형이나 심볼형에 속하지 않은 값은 문자열로 자동 형변환 된다.

예시를 살펴보자. 키에 숫자 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를 할당했는데 무시된 것을 확인할 수 있다.

'in' 연산자로 프로퍼티 존재 여부 확인하기

자바스크립트 객체의 중요한 특징 중 하나는 다른언어와는 달리, 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined를 반환한다는 것이다.

이런 특징을 응용하면 프로퍼티 존재 여부를 쉽게 확인할 수 있다.

let user = {};

alert( user.noSuchProperty === undefined); // true는 '프로퍼티가 존재하지 않음'을 의미

이렇게 undefined와 비교하는 것 이외에도 연산자 in을 사용하면 프로퍼티 존재 여부를 확인할 수 있다.

문법은 다음과 같다.

"key" in object

예)

let user { name: "John", age: 30 };

alert( "age" in user ); // user.age가 존재하므로 true
alert( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false

in 왼쪽엔 반드시 프로퍼티 이름 이 와야 한다. 프로퍼티 이름은 보통 따옴표로 감싼 문자열이다.

따옴표를 생략하면 아래 예시와 같이 엉뚱한 변수가 조사 대상이 된다.

let user = { age:30 };

let key = "age";
alert( key in user ); // true, key에 저장된 값("age")을 사용해 프로퍼티 존재여부를 확인

그런데 이쯤 되면 "undefined랑 비교해도 충분한데 왜 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인 경우는 흔치 않다. 값을 '알 수 없거나(unkown)' 값이 '비어 있다(empty)' 것을 나타낼때 주로 null을 사용한다. 위 예시에서 in연산자는 자리에 어울리지 않은 초대손님처럼 보인다.

'for...in' 반복문

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)했다는 점에 주목해 주길 바란다. 반복 변수명은 자유롭게 정할 수 있다. for (let prop in obj) 같이 key 말고 다른 변수명을 사용해도 괜찮다.

객체 정렬 방식

객체와 객체 프로퍼티를 다루다 보면 "프로퍼티엔 순서가 있을까?" 라는 의문이 생기기 마련이다. 반복문은 프로퍼티를 추가한 순서대로 실행될지, 그리고 이 순서는 항상 동일할지 궁금하다.

답은 간단하다. 객체는 '특별한 방식으로 정렬' 된다. 정수 프로퍼티(integer priperty)는 자동으로 정렬되고, 그 외의 프로퍼티는 객체에 추가한 순서 그대로 정렬된다.

아래 객체엔 국제전화 나라 번호가 담겨있다.

let codes = {
  "49": "독일",
  "41": "스위스",
  "44": "영국",
  // ...,
  "1": "미국"
};

for (let code in codes) {
  alert(code); // 1, 41, 44, 49
}

현재 개발 중인 애플리케이션의 주 사용자가 독일인이라고 가정해 보자. 나라 번호를 선택하는 화면에서 49가 맨 앞에 오도록 하는 게 좋을 것이다.

그런데 코드를 실행해 보면 예상과 전혀 다른 결과가 출력된다.

  • 미국(1)이 첫 번째로 출력된다.
  • 그 뒤로 스위스(41), 영국(44), 독일(49)이 차례대로 출력된다.

이유는 나라 번호(키)가 정수여서 1, 41, 44, 49 순으로 프로퍼티가 자동 정렬되었기 때문이다.

정수 프로퍼티? 그게 뭘까?

'정수 프로퍼티' 라는 용어는 변형 없이 정수에서 왔다 갔따 할 수 있는 문자열 을 의미한다.

문자열 "49"는 정수로 변환하거나 변환한 정수를 다시 문자열로 바꿔도 변형이 없기 대문에 정수 프로퍼티이다.

한편, 키가 정수가 아닌 경우엔 작성된 순서대로 프로퍼티가 나열된다.

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
}

이제 원하는 대로 독일 나라 번호가 가장 먼저 출력되는 것을 확인할 수 있다.

요약 ✔

객체는 몇 가지 특수한 기능을 가진 연관 배열(associative array) 이다.
객체는 프로퍼티(키-값 쌍)를 저장한다.

  • 프로퍼티 키는 문자열이나 심볼이여야 한다. 보통은 문자열이다.
  • 값은 어떤 자료형도 가능하다.

아래와 같은 방법을 사용하면 프로퍼티에 접근할 수 있다.

  • 점 표기법: obj.property
  • 대괄호 표기법: obj["property"]. 대괄호 표기법을 사용하면 obj[varWithKey] 같이 변수에서 키를 가져올 수 있다.

객체엔 다음과 같이 추가 연산자를 사용할 수 있다.

  • 프로퍼티를 삭제하고 싶을 때: delete obj.prop
  • 해당 key를 가진 프로퍼티가 객체 내에 있는지 확인하고자 할 때: "key" in obj
  • 프로퍼티를 나열할 때: for (let key in obj)

지금까진 '순수 객체(plain object)'라 불리는 일반 객체에 대해 학습했다.
자바스크립트에는 일반 객체 의외에도 다양한 종류의 객체가 있다.

  • Array - 정렬된 데이터 컬렉션을 저장할 때 쓰임
  • Date - 날짜와 시간 정보를 저장할 때 쓰임
  • Error - 에러 정보를 저장할 때 쓰임
  • 기타 등등

객체마다 고유의 기능을 제공하는데, 사람들은 종종 'Array' 타입이나 'Date 타입' 이라는 용어를 쓰곤 한다. 사실 Array와 Date는 독립적인 자료형이 아니라 '객체'형에 속한다. 객체에 다양한 기능을 넣어 확장한 또 다른 객체이다.

객체는 다재다능한 자료구조로 자바스크립트에서 그 영향력이 막강하다.

과제 ✔

객체야 안녕?

코드 한 줄로 아래 문제를 각각 풀어보자.

  1. 빈 객체 user를 만듭니다.
  2. user에 키가 name, 값이 John인 프로퍼티를 추가하세요.
  3. user에 키가 surname, 값이 Smith인 프로퍼티를 추가하세요.
  4. name의 값을 Pete로 수정해보세요.
  5. user에서 프로퍼티 name을 삭제하세요.

해답

let user = {};
user.name: "John",
user.surname: "Smith"
user.name = "Pete";

delete user.name;

객체가 비어있는지 확인하기

객체에 프로퍼티가 하나도 없을 경우 true, 그렇지 않은 경우 false를 반환해주는 함수 isEmpty(obj) 를 만들어 보자.
아래와 같이 동작해야 한다.

let schedule = {};

alert( isEmpty(schedule) ); // true

schedule["8:30"] = "get up";

alert( isEmpty(schedule) ); // false

해답

객체 프로퍼티를 대상으로 반복문을 실행하다가 프로퍼티가 하나라도 있으면 그 즉시 false를 반환하게 코드를 작성하면 된다.

function isEmpty(obj) {
  for (let key in obj) {
    // if the loop has started, there is a property
    return false;
  }
  return true;
}

프로퍼티 합계 구하기

모든 팀원의 월급에 대한 정보를 담고 있는 객체가 있다고 해보자.

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
}

}
모든 팀원의 월급을 합한 값을 구하고, 그 값을 변수 sum에 저장해주는 코드를 작성해보자. sum390이 저장되어야한다.

주의: salaries가 비어있다면 sum0이 저장되어야 한다.

해답

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
}

let sum = 0;
for (let key in salaries) {
  sum += salaries[key];
}

alert(sum);

프로퍼티 값 두 배로 불리기

객체 obj 의 프로퍼티 값이 숫자인 경우 그 값을 두 배 해주는 함수 mulitplyNumeric(obj)을 만들어보자.

// 함수 호출 전
let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};

multiplyNumeric(menu);

// 함수 호출 후
menu = {
  width: 400,
  height: 600,
  title: "My menu"
};

multiplyNumeric은 아무것도 반환하지 않아도 괜찮다. 객체 자체를 수정해주기만 하면 된다.

힌트) typeof를 사용하면 프로퍼티 값이 숫자인지 확인할 수 있다.

해답

function multiplyNumeric(obj) {
  for (let key in obj) {
    if (typeof obj[key] == 'number') {
      obj[key] *= 2;
    }
  }
}

좋은 웹페이지 즐겨찾기