Object를 만드는 4가지 방법

제일 좋은 방법이란 없다. 자바스크립트 또는 타입스크립트에 따라서도 상황은 달라진다. 모든 방법을 잘 익히고 활용해 보자.

객체

자바스크립트의 객체는 [key, value]의 쌍(pair)으로 구성된 아주 단순한 컬렉션(collection)이다.

또한 객체는 외부에 드러나는 행위(behavior)와 행위를 지원하기 위한 상태(state)를 묶어주는 편리한 단위이다. 만약 객체가 없다고 생각해 본다면, 하나의 프로그램의 상태가 마구잡이로 섞여서 프로그램을 수정하기도 어렵고 이해하기도 어렵기 때문에 유지보수도 힘들 것이다. 하지만 객체를 사용하여 참조할 수 있는 상태의 네임스페이스가 훨씬 작아지므로, 특정 변화에 의해 상태가 어떤 영향을 받을지 분석하기 더 쉬워진다.

또한 객체의 목표 중 하나는 저장소를 관리하는 것이다. 각 객체는 조그마한 메모리를 갖고 있는 별도의 작은 컴퓨터라 볼 수 있다.

1. new Object

const obj = new Object();

2. 객체 리터럴

가장 접하기 쉽고 일반적으로 객체를 만드는 방법이다.

const person = {name: 'jacob', age: 20}

2-1. 새로운 속성 추가하기

person.address = 'seoul ...'
person['full name'] = 'jacob'
person.address = 'korea ...'

2-2. 속성 삭제하기

// 만약 타입스크립트였다면, isAdmin의 속성이 옵셔널이어야 한다.
delete person.isAdmin

3. 클래스

클래스를 만들고, 그 클래스의 인스턴스 객체를 만들 수 있다.

class Person {
  _name;
  _age;
  
  constructor(name, age){
    this._name = name;
    this._age = age;
  }
}

const jacob = new Person('jacob', 30);
console.log(jacob._age); // 30

3-1. getter, setter

객체 외부에서는 객체 내부의 속성을 마음대로 변경할 수 있다. 예를 들면 아래와 같다.

jacob._age = 30;
jacob._age = -40000;

나이가 음수인 경우는 없을 것이다. 이런 변경을 제어할 수 없기 때문에 등장한 게 gettersetter이다. 이 방을 사용하면 객체 내부에서는 함수로 사용되지만 외부에서는 속성처럼 보이게 할 수 있다.

class Person {
  _name;
  _age;
  
  constructor(name, age){
    this._name = name;
    this._age = age;
  }
  
  set age(num) {
    if(num >= 0) {
      this._age = num
    }
  }
  
  get age() {
    return this._age + '살';
  }
}

위에처럼 정의했다면 이제 다시 age를 조회도 해보고 값도 변경해 보자.

const jacob = new Person('jacob', 30);

jacob.age; // 30살
jacob.age = -30;
jacob.age; // 30살
jacob.age = 31;
jacob.age; // 31살

하지만 이 경우에는 약간의 문제점이 아직 남아있다.

jacob._age = 30;
jacob.age; // -30

외부에서 내부의 _age 값에 접근할 수 있기 때문에 getter, setter를 만들었지만, 직접 접근하여 값을 수정해 버린다면 무용지물이 되는 것이다. 이 경우 내부 필드의 값을 private 값으로 변경해 주면 된다.

자바스크립트에서 private field#을 사용해서 만들 수 있다.

class Person {
  #name; // 속성명과 메서드 명이 같아 _를 붙여주었지만, 이 경우 필요 없게 되어 제거했다.
  #age;
  
  constructor(name, age){
    this.#name = name;
    this.#age = age;
  }
  
  set age(num) {
    if(num >= 0) {
      this.#age = num
    }
  }
  
  get age() {
    return this.#age + '살';
  }
}

const jacob = new Person('jacob', 30);
jacob.#age = -30; // SyntaxError

이처럼 gettersetter를 이용하면, 내부의 값을 보호하면서도 외부에는 필요한 것만 노출할 수 있게 만들 수 있다.

3-2. 언제 getter, setter를 사용하지?

위의 예제처럼 추가 로직이 있을 경우에는 getter, setter가 유용하다. 만약 추가 로직이 없다면, 코드량만 늘뿐 아무런 실익이 없기 때문에 그냥 공개(public) 필드를 노출하는 것이 좋다.

3-3. private 키워드

#을 통해서 private 필드를 만드는 것은 다른 언어를 사용해봤다면 조금 어색하게 느껴질 수 있다. 하지만 타입스크립트를 사용하고 있다면 private 키워드를 통해 어색함을 해결할 수 있다.

class Person {
  private age;
}

3-4. private vs #

비공개 필드를 만드는 두 가지 방법이다. 아 물론 타입스크립트를 사용한다면 말이다. 아무런 차이가 없어 보이지만, 두 키워드 간에는 차이가 존재한다. private 키워드는 soft private이기 때문에 private 키워드로 선한한 필드는 컴파일 후에는 접근할 수 있다.

class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const person1 = new Person('jacob')
// 컴파일 후 자바스크립트 파일에서는...
person1.name; // 접근 가능

또한 자바스크립트로 컴파일하기 전이라도 사실 대괄호를 사용한다면 escape hatches가 발생하여 해당 비공개 필드에 접근할 수 있다.

class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const person1 = new Person('jacob')
person1['name']; // 접근 가능..!

반면, #으로 작성시 hard private이 되기 때문에 위의 방식들로는 접근할 수 없다.

class Person {
 #name;
  
  constructor(name) {
    this.#name = name; 
  }
}

// ----->

var Person = function Person(name) {
  _classCallCheck(this, Person);

  _classPrivateFieldInitSpec(this, _name, {
    writable: true,
    value: void 0
  });

  _classPrivateFieldSet(this, _name, name);
};

4. Object.create()

Object.create() 메서드는 기존 객체를 새로 만든 객체의 프로토 타입으로 사용하여 새로운 객체를 만드는 메서드이다.

Object.create(proto)

Object.create(proto, propertiesObject)

위에 방법들에 비해 잘 사용되진 않지만, 이 메서드만의 장점도 존재한다. 예를 들어 자바스크립트로 객체를 만드는데 특정 속성이 지워지지 않도록 하려면 어떻게 해야 될까?

const person = {name: 'jacob', age: 30} 
person.name = 'cob'; // <-- 이것을 막고 싶어! (writable)
delete person.name; // <-- 이것을 막고 싶어! (configurable)

만약 위의 요구사항을 만족하고 싶다면, 아래와 같이 하면 된다.

const person = Object.create(null, {
  name: {
    value: 'jacob',
    writable: false, // 참고로 false가 기본값이다, 즉 false시 readonly 상태가 된다.
    configurable: false, // 참고로 false가 기본값이다
  },
  age: {
    value: 30,
    writable: true,
    configurable: true,
  }
});

propertiesObject 인수를 사용하면서, nameage의 속성을 각각 반대로 지정하였다. 이 결과는 어떻게 될까?

결론부터 말하자면, name의 경우 지울 수도 없으며 새로운 값을 할당할 수가 없으나, age의 경우 삭제하거나 새로운 값을 할당할 수 있다.

아래는 같은 방식으로 age의 값을 조작해 보았다.

타입스크립트를 사용했다면 간단히 해결될 수도 있었지만, 어떤 환경에서 개발을 하게 될지 모르기 때문에 일단 모든 방법을 다 알아두자.

Last updated