객체: 기본 | 생성자, 심볼형, 원시형 변환

345·2023년 2월 6일

모던 JavaScript

목록 보기
5/23

🔔 생성자 함수

객체 리터럴 { } 을 사용하는 방법 대신에, 규격만을 전달하여
유사한 객체 여러 개를 만드는 방법이 있습니다.
new 연산자와 생성자 함수를 이용하여 객체를 생성합니다.

생성자 함수는 일반 함수와 기술적으로 차이가 없으나, 사용할 때 다음과 같은 규칙을 따라야 합니다.

  1. 함수 이름의 첫 글자는 대문자로 시작
  2. 반드시 new 연산자를 붙여 함수 실행

new 연산자를 사용하면 특별한 동작을 수행합니다.
앞에 new 연산자를 붙여 사용하면 대문자로 시작하지 않아도 모든 함수가 생성자 함수로 동작할 수 있습니다.

생성자 함수를 사용하는 예시입니다.

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("보라");

alert(user.name); // 보라
alert(user.isAdmin); // false

new 연산자를 사용한 생성자 함수는 일반 함수와 달리 추가적인 동작이 수행됩니다.

✅ 생성자 함수의 동작

생성자 함수를 실행했을 뿐인데 user 변수에 객체가 할당됨을 위 예시에서 확인할 수 있습니다.
코드에 나타나지 않는 실제 동작은 다음과 같다고 볼 수 있습니다.

function User(name) {
  // this = {};  (빈 객체가 암시적으로 만들어짐)

  // 새로운 프로퍼티를 this에 추가함
  this.name = name;
  this.isAdmin = false;

  // return this;  (this가 암시적으로 반환됨)
}

동작은 다음과 같습니다.

  1. this 에 할당할 빈 객체 생성
  2. 프로퍼티를 this 의 객체에 추가
  3. this 반환

따라서, user 변수는 this 가 가리키는 객체를 참조하게 됩니다.


생성자 함수에 return 문이 있다면 ❓

this 를 반환해야 하는 생성자 함수에 별도의 return 문이 존재한다면 어떻게 될까요?
결과는 반환값에 따라 달라집니다.

  • 반환값이 객체: 해당 객체 반환
  • 반환값이 객체가 아님: 무시하고 this 반환

this 를 반환해야 하므로 생성자 함수에는 거의 return 문이 없습니다.

생성자 내 메서드

생성자 함수를 사용하여 프로퍼티에 메서드를 할당하는 것도 가능합니다.

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "제 이름은 " + this.name + "입니다." );
  };
}

let bora = new User("이보라");

bora.sayHi(); // 제 이름은 이보라입니다.

🟡 심볼형

객체의 프로퍼티 키에는 문자형과 심볼형 자료만이 올 수 있습니다.
Symbol 은 원시형 데이터로, 유일무이한 식별자를 만드는 데 사용됩니다.

let id = Symbol("id"); // id 라는 심볼 이름 => 심볼 설명

id 라는 심볼을 만들고 "id" 라고 설명을 붙였습니다.
설명 문자열은 없어도 됩니다.

심볼의 특징은 다음과 같습니다.

  • 유일성을 보장
  • 이름이 같아도 다르게 취급
  • 문자형으로 자동 형변환되지 않음
    • 문자형 변환을 위해선 .toString() 메서드 사용

숨김 프로퍼티

심볼을 프로퍼티 키로 사용하면 외부 코드에서 접근할 수 없고 값도 덮어쓸 수 없는 프로퍼티를 만들 수 있습니다.

let age = Symbol("age");

let user = { // 서드파티 코드에서 가져온 객체
  name: "John",
  [age]: 20 // 심볼 변수 age 를 키로 사용
};

let id = Symbol("id");

user[id] = 1; // id 심볼을 키로 사용하여 값 1 할당

alert( user[id] ); // 심볼을 키로 사용해 데이터에 접근

서로 이름이 같더라도 충돌하지 않는 식별자를 만들고 싶을 때 심볼을 활용합니다.

배제되는 심볼

심볼은 특정한 경우에 배제됩니다.

  • for...in 반복문 : 키가 심볼인 프로퍼티는 그냥 넘어감
  • Object.keys(obj) : 키가 심볼인 경우는 반환 ❌

반면, Object.assign 의 경우 키가 심볼이어도 배제하지 않습니다.

전역 심볼

심볼은 이름이 같더라도 별개로 취급되지만, 반대로 이름이 같으면 같은 개체로 취급하길 원할 수도 있습니다.
전역 심볼 레지스트리를 사용하여 심볼을 생성하고, 읽어오면 됩니다.

  • Symbol.for(key) 을 사용하여 전역 심볼을 생성하고 읽어옵니다.
// 전역 레지스트리에서 심볼 읽기
let id = Symbol.for("id"); // 심볼이 존재하지 않으면 새로운 심볼 생성

// 동일한 이름을 이용해 심볼을 다시 읽기
let idAgain = Symbol.for("id"); // 심볼이 존재하므로 그 값을 읽어옴

// 두 심볼은 동일
alert( id === idAgain ); // true

이름을 이용하여 전역 심볼을 생성하고 읽어옵니다.

  • Symbol.keyFor(symbol) 을 이용하여 심볼 이름 얻기
// 이름을 이용해 심볼을 찾음
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// 심볼을 이용해 이름을 얻음
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

해당 심볼의 이름을 얻어옵니다.


🔥 객체의 원시형 변환

객체로 연산을 수행할 때, 객체를 출력할 때 등의 경우 자동 형 변환이 일어납니다.
객체를 원시값으로 변환하여 연산을 수행하는 것이죠.

객체의 형 변환은 다음과 같이 적용됩니다.

  • Boolean 형 변환 : 무조건 true 반환
  • 숫자형 변환이나 문자형 변환의 경우 toPrimitive, toString, valueOf 메서드를 객체에 구현하여 사용

객체의 형 변환에는 hint 값을 사용합니다.
hint 는 변환을 기대하는 자료형, 목표로 하는 자료형으로 생각하면 됩니다.
숫자 연산 수행 시 hintnumber, 출력할 때에는 string 이 됩니다.
그 외의 애매한 경우는 default 가 되며, number 와 동일하게 처리합니다.

Symbol.toPrimitive

내장 심볼을 이용한 toPrimitive 메서드를 구현하면 객체의 형 변환에 해당 메서드를 호출합니다.
Symbol.toPrimitive 는 반드시 원시형 자료를 반환해야 합니다.

let user = {
  name: "John",
  money: 1000,

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money; 
    // hint 가 string 이면 이름을, 그 외의 경우엔 money 를 반환
  }
};

// 데모:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500

메서드 하나로 모든 종류의 형 변환을 다룰 수 있습니다.

toString 과 valueOf

Symbol.toPrimitive 가 없는 경우 toString 이나 valueOf 메서드를 사용합니다.
toString 이나 valueOf 는 객체를 반환해도 에러가 나지 않습니다.

  • hintstring 이면 toString 을 먼저 사용, toString 이 없다면 valueOf 사용
  • 그 외의 경우에는 valueOf 를 먼저 사용, 이후에 toString 사용

toStringvalueOf 는 기본적으로 다음과 같은 동작을 수행합니다.

  • toString 은 문자열 [object Object] 반환
  • valueOf 는 객체 자신을 반환

valueOf 는 객체 자신을 반환하므로 그 결과가 무시됩니다.
따로 정의하지 않은 경우에는 그냥 이 메서드가 존재하지 않는다고 생각하면 됩니다.

구현 예시

toStringvalueOf 메서드를 구현해봅시다.

let user = {
  name: "John",
  money: 1000,

  // hint가 "string"인 경우
  toString() {
    return `{name: "${this.name}"}`;
  },

  // hint가 "number"나 "default"인 경우
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

hint 에 맞게 각 메서드가 호출되는 것을 확인할 수 있습니다.

profile
기록용 블로그 + 오류가 있을 수 있습니다🔥

0개의 댓글