(6장) 객체 [자바스크립트 완벽 가이드 7판]

iberis2·2023년 1월 16일
0

1. 객체 소개

키(또는 네임): 값 로 이루어진 한 쌍을 프로퍼티(property)라고 하며, 객체는 0개 이상의 프로퍼티를 가지고, {} 중괄호 안에 표현한다.

  • 키는 문자열 또는 심볼 타입이고, 값은 모든 타입이 가능하다.
  • 키에 빈 문자열""도 가능하다
  • 같은 이름의 프로퍼티는 존재할 수 없다

문자열, 숫자, 심벌, boolean값(true, false), null, undefined 가 아닌 값은 전부 객체이다.

  • 문자열, 숫자 boolean은 객체는 아니지만 불변인 객체처럼 행동할 수 있다.

자신만의 프로퍼티 외에도 프로토타입(prototype)이라고 불리는 다른 객체에서 프로퍼티를 상속할 수도 있다.

  • 상속된 프로퍼티가 아닌 자신만의 프로퍼티를 own property라고 한다.
  • 매서드는 프로퍼티 값이 함수인 것을 말하며, 일반적으로 상속된 프로퍼티이다.

객체의 flag 속성 - writable, enumerable, configurable

2. 객체 생성

2-1. 객체 리터럴

{키(또는 네임): 값, 키: 값,} 형태이다.

  • 키에는 문자열(또는 심볼)이 가능한데, 따옴표 없이 사용할 수 있다.
    • 프로퍼티 네임(키) 규칙
      • 첫 글자는 반드시 문자, 밑줄_, 달러 기호$ 중 하나로 시작
      • 띄어쓰기, 하이픈- 금지
        (단, 따옴표로 감싸는 경우 사용 가능)
      • ES2015 이후 표기법이 더욱 간단해짐
      • 프로퍼티 네임(키)이 변수이면, 할당된 값 생략 가능
      • 프로퍼티 네임을[] 안에 표현하는 경우 표현식으로 표현 가능
      • 프로퍼티 값이 함수일 경우 :function을 생략 가능
        • 함수 이름으로 "따옴표와 띄어쓰기", [표현식], 심볼 모두 가능하다
let 변수이름 = "생략가능";
let 함수의리턴값 = () => "가능";
function 함수이름() { return ("이것도 생략가능"); }

let myObj = {
  따옴표없이: "띄어쓰기 불가능",
  "따옴표-있으면": "띄어쓰기와 하이픈-가능",
  변수이름,
  함수이름,
  ["표현식" + "이렇게"]: "가능",
  [함수의리턴값()]: "함수의 리턴값",
  함수: function () { console.log("이 매서드를");},
  same함수() { console.log("이렇게 생략 가능");},
};

console.log(myObj);
/* { 
  '따옴표없이': '띄어쓰기 불가능',
  '따옴표-있으면': '띄어쓰기와 하이픈-가능',
  '변수이름': '생략가능',
  '함수이름': [Function: 함수이름],
  '표현식이렇게': '가능',
  '가능': '함수의 리턴값',
  '함수': [Function: 함수],
  'same함수': [Function: same함수]
}*/
myObj.함수(); // 이 매서드를
myObj.same함수(); // 이렇게 생략 가능

2-2. neew

new Object() 생성자 함수 호출을 통해 객체도 만들 수 있다.

let obj = new Object(); // {} 빈 객체 생성
let ary = new Arrary(); // [] 빈 배열 생성
let sampleDate = new Date(); // 현재 시간 나타내는 Date 객체 생성
let sampleMap = new Map(); // Map(0) {} 빈 맵 생성

2-3. Object.create()

프로토타입

자바스크립트에는 Prototype Link 와 Prototype Object라는 것이 존재한다. 그리고 이 둘을 통틀어 Prototype이라고 부른다.

[참고] __proto__ 보다는 Object.getPrototypeOf()를 써야한다고 한다 MDN

모든 객체는 프로토타입이 있지만, prototype 프로퍼티(property)가 있는 객체는 비교적 적다. (상속해주는 객체가 모두 prototype 프로퍼티를 가지고 있는 것은 아님!)

(Object 객체를 제외하고) 객체는 자신과 연결된 두 번째 객체인 __proto__(프로토타입)를 갖는다. 이 __proto__ 객체가 상속 해주는 객체의 프로퍼티와 연결되어있다

  • 상속받는 객체는 자신의 __proto__ (프로토타입) 객체(즉 프로토타입 링크)를 통해서 상속해주는 (부모)객체의 prototype 프로퍼티에 접근할 수 있다.
  • 객체 리터럴 { } 을 통해 만들어진 객체의 프로토타입은 모두 Object.prototype의 프로퍼티를 상속받는다.
  • 생성자 함수는 prototype 프로퍼티를 갖는 객체를 가진다.
    new생성자(constructor)로 만들어진 인스턴스 객체는 생성자함수의 prototype 프로퍼티를 상속받아 자신의 (__proto__ 프로토타입 체인으로써) 프로퍼티로 사용한다.

이렇게 이어지는 프로토타입 객체 사이의 연결을 프로토타입 체인이라고 한다

Object.create()

Object.create(상속해주는 객체)
파라미터에 null을 쓰면 Object 와의 프로토타입 체인도 끊겨서, 객체의 내장 메서드도 사용할 수 없다.

// 일반적인 빈 객체 {} 를 만들기 위해서는 아래와 같은 코드를 사용해야한다.

let emptyObj = Object.create(Object.prototype);

3. 프로퍼티 검색과 설정

프로퍼티 표기법에는 두 가지가 있다.
1. dot notation(점 표기법) 객체.key
2.bracket notation(대괄호 표기법) 객체['key']
이 중 대괄호 표기법 안에는 단순 문자열 외에도 문자열 표현식(변수, + 로 이어진 문자열, 리턴값이 문자열인 함수 등)이 가능하다.

인덱스가 숫자가 아닌 문자열인 배열을 연관배열이라고 한다.
자바스크립트의 객체도 연관배열이다.

상속

프로토타입 체인은 계속 이어질 수 있다.
객체의 프로퍼티를 탐색할 때, 바로 직전에 상속해준 객체의 프로토타입에서 발견할 수 없으면 그 상위 객체를 탐색하고, 최종적으로 Object 까지 거슬러 올라가 탐색하여 반환한다.

(own property가 아닌) 상속받은 프로퍼티와 동일한 프로퍼티 네임의 프로퍼티를 할당하면,
writable = false이면 할당이 허용되지 않는다.
writable = true 이면 own property로 할당된다.
상속해주는 객체의 프로퍼티에는 아무 영향이 없다.

let obj1 = { x: 1, a: 1}
let obj2 = Object.create(obj1);
obj2.y = 2;
let obj3 = Object.create(obj2);
obj3.x = 3; // obj3의 own property로 할당된다. obj1.x에는 영향이 없다.

console.log(obj3.a); // 1 (obj.1 에서 상속-상속 받았다)
console.log(obj3.x); // 3

conxole.log(obj1.x); // 1 

접근 에러

존재하지 않는 프로퍼티를 검색하는 것은 에러가 아닌 undefined이다.
하지만 존재하지 않는 프로퍼티의 프로퍼티(undefined 또는 null에 대한 프로퍼티) 를 검색하면 TypeError가 발생한다.

에러를 방지하기 위해 2가지 방법을 사용할 수 있다.

  • 조건문 또는 조건 연산자
  • ?. 조건부 프로퍼티 접근 연산자
// book.movie.series 가 존재하는 프로퍼티인지 확실하지 않을 때
let book = { title: "해리포터" };

let series = undefined;

// 조건문
if (book) {
  if (book.movie) {
    series = book.movie.series;
  }
}

// && 연산자
series = book && book.movie && book.movie.series;

console.log(series); // undefined

// ?. 연산자
let series = book?.movie?.series;
console.log(series); // undefined

프로퍼티 추가(또는 수정) 실패

null 이나 undefined에 프로퍼티 설정하는 경우 TypeError 발생한다.

아래 두 경우는 일반모드에서는 조용히 실패하고, 'use strict' 모드에서는 Type Error가 발생한다.
1. 똑같은 이름의 프로퍼티가 own property 또는 상속 property로 이미 존재하고, 읽기 전용(writable = false)일 때
(상속된 읽기 전용 프로퍼티도 같은 이름의 own property로 덮을 수 없다.)

  1. own property로 없고, 접근자 프로퍼티 set(){}도 없는데, 객체 속성 extensible = false 일 때

4. 프로퍼티 삭제

프로퍼티의 flag 속성configurable = true 일 때, delete 연산자로 own property를 삭제할 수 있다. (상속된 프로퍼티는 삭제 불가)

configurable = false인 프로퍼티를 삭제 시도 하면,

  • 일반 모드에서는 조용히 실패하고
    (겉으로는 아무일도 일어나지 않지만 콘솔에 delete 연산자를 확인해보면 false 출력),
  • 'use strict' 모드에서는 Type Error 가 발생한다.

delete 연산자를 콘솔에서 확인해보면,
삭제 성공했을 때, 존재하지 않는 프로퍼티를 삭제 시도 했을 때, 프로퍼티 접근 표현식이 아닌 표현식을 삭제 시도 했을 때(아무 현상도 일어나지 않지만) true 로 평가된다.

let obj = { x: 1 };

console.log(delete obj.y); // true
console.log(delete obj.x); //true
console.log(delete 1); // true


5. 프로퍼티 테스트

객체 안에 프로퍼티가 있는지 확인할 때 다음 연산자와 매소드를 사용할 수 있다.
1. in 연산자
2. 프로퍼티 !== undefiend
3. hasOwnProperty()
4. propertyIsEnumerable()

let obj = { x: 1, a: undefined };
let 상속obj = Object.create(obj);

console.log("x" in obj); // true
console.log("a" in obj); // true
console.log("y" in obj);  // false
console.log("x" in 상속obj); // true

console.log(obj.x !== undefined); // true
console.log(obj.a !== undefined); // false (프로퍼티 값으로 undefined 할당된 것을 선별하지 못한다)
console.log(obj.y !== undefined); // false
console.log(상속obj.x !== undefined); // true

console.log(obj.hasOwnProperty("x")); // true
console.log(obj.hasOwnProperty("y")); // false
console.log(상속obj.hasOwnProperty("x")); // false (own property가 아닌 경우 false 즉, 상속된 property는 false)

// hasOwnProperty보다 더 제한적이다.
// own property 이면서 enmerable = true 여야만 true
console.log(obj.propertyIsEnumerable("x")); // true
console.log(obj.propertyIsEnumerable("y")); // false
console.log(상속obj.propertyIsEnumerable("x")); // false
console.log(Object.prototype.propertyIsEnumerable("toString")); // false (내장 매서드이지만 enumerable = false인 매서드)

6. 프로퍼티 열거

객체의 프로퍼티 열거방법

  1. for in 문 : 상속 여부를 구분하지 않고 열거 가능(enumerable=true)한 프로퍼티면 루프 바디에서 실행된다.
  • 상속된 프로퍼티의 열거를 막고 싶으면 루프 바디 안에 조건문으로 hasOwnProperty()continue 로 건너뛰게 할 수도 있다.
  1. 객체의 프로퍼티 네임을 배열로 저장해서 for of 루프를 사용하는 게 더 쉬울 때가 많다.
  • Object.keys() : (enmuerable = true) && own property && 프로퍼티 네임이 symbol 인 프로퍼티 네임만 반환
  • Object.getOwnPropertyNames() : 이름이 문자열이면 열거 불가인 own property의 네임도 반환(상속된 프로퍼티 네임은 반환 X)
  • Object.getOwnPropertySymbols() : 열거 가능 여부를 따지지 않고 프로퍼티 네임이 symbol 이면 반환
  • Reflect.ownKeys() : 열거 가능 여부를 따지지 않고, 문자열과 symbol 구분하지 않고, own property 이면 전부 반환
  1. 프로퍼티 열거 순서
    own property의 네임이 아래 순서에 따라 열거된다.
  • ① 0 ② 양수 ③ 음수, 소수, 문자열 중에서 추가된 순서대로 ④ 심벌

  • for in 문의 경우 own property가 위 순서대로 먼저 열거되고, 프로토타입 체인을 따라 올라가며 같은 순서로 열거되는데,

    • 같은 이름의 프로퍼티가 이미 열거 됐다면 열거하지 않고,
    • 열거 불가프로퍼티였어서 열거하지 않고 지나간 프로퍼티가, 같은 이름의 프로퍼티로 부모 객체에서 나타나도 열거하지 않는다.

7. 객체 확장

  1. 프로퍼티 복사 : for in문이나 for of문 이용
let target = { x: 1, y: 3 }, source = { x: 2, z: 4 };

for (let key of Object.keys(source)) {
 /* 중복되지 않은 프로퍼티만 복사하고 싶다면 해당 조건 추가 */
// if (!(key in target)) { 
  target[key] = source[key];
// }
}
console.log(target); // { x: 2, y: 3, z: 4 }
  1. Object.assign(복사받을 객체, 복사할 원본 객체) : 복사할 원본 객체에서 열거 가능한 own property(네임이 symbol인 것 포함)를 복사 받을 객체에 복사한다.
  • 같은 이름의 프로퍼티는 덮어쓰여진다.
  • 복사할 원본 객체에 gettter가 있거나 복사 받을 객체에 setter가 있다면 복사 도중 호출되긴 하지만 매서드 자체를 복사하지는 않는다.
let target = { x: 1, y: 3 },
  source = { x: 2, z: 4 };

Object.assign(target, source);
console.log(target); // { x: 2, y: 3, z: 4 }

8. 객체 직렬화

객체 직렬화는 객체를 문자열로 변환하는 작업이다.

JSON.stringify() : 객체 → 문자열로 직렬화
JSON.parse() : (직렬화 된 객체의)문자열 → 객체로 되돌림

열거 가능한 자체 프로퍼티만 직렬화한다. 직렬화할 수 없는 프로퍼티는 생략된다.

  • 직렬화 가능한 프로퍼티: 객체, 배열, 문자열, 유한한 숫자, true, false, null
    • NaN, Infinity, -Infinity는 null로 직렬화된다
  • 직렬화 불가능한 프로퍼티: 함수, RegExp, Error 객체, undefined 값은 직렬화하거나 복원할 수 없다

9. 객체 메서드

toString(){}

호출한 객체의 값을 나타내는 문자열을 반환한다.
객체를 문자열로 변환하려고 하면 자바스크립트에서 내부적으로 toString() 매서드를 호출(사용)해서 결과값을 반환한다.
하지만 모든 객체를 [object Object]로 리턴하기 때문에 ,
유용하게 사용하기 위해서 toString() 매서드를 클래스마다 직접 재정의해서 사용하기도 한다.

let 친구 = {
  학원친구: "승현",
  학교친구: "지희",
  toString() {
    console.log(`학원에 ${this.학원친구}, 학교에 ${this.학교친구}가 있다`);
  },
};

// 객체를 문자열로 변환하려고 하면 toString() 매서드가 호출된다.
String(친구); // 학원에 승현, 학교에 지희가 있다

valueOf()

객체를 문자열이 아닌 다른 기본타입(주로 숫자)으로 변환하려고 할 때 호출한다.
기본값이 들어가야하는 곳에 객체를 넣으면 자바스크립트에서 내부적으로 valueOf() 매서드를 호출(사용)해서 결과값을 반환한다.

Date 클래스의 valueOf()는 날짜를 숫자로 변환하여 서로 다른 날짜를 시간 순서로 비교(<, >)할 수 있게 해준다.

toJOSN()

Object.prototype에 실제로 정의되어 있진 않지만 JSON.stringify()로 객체를 직렬화할 때, 호출된다.
직렬화할 객체 내부에 따로 toJOSN()메서드가 정의되어 있으면,
JSON.stringify()를 사용했을 때, 정의된 메서드가 실행된 후 리턴된 값이 직렬화된다.

10. 확장된 객체 리터럴 문법

10-1. 프로퍼티 네임으로 심볼

ES6 이후 심벌을 변수나 상수에 할당하고, [ 변수(또는 상수) ] 로 표기하면, 프로퍼티 네임으로 symbol도 쓸 수 있다.
symbol은 같은 문자열을 인자로 삼아도 서로 다른 심볼이 되기 때문에 새로 프로퍼티를 추가할 때 기존 프로퍼티와 충돌을 막을 수 있다.

10-2. ...sprad 연산자

... sprad 연산자를 이용해 프로퍼티를 다른 객체로 복사할 수 있다.
같은 이름의 프로퍼티는 마지막에 오는 값으로 덮어씌워진다.
own property만 분해할 뿐, 상속된 프로퍼티에는 적용되지 않는다.

10-3. getter와 setter

profile
React, Next.js, TypeScript 로 개발 중인 프론트엔드 개발자

0개의 댓글