[JavaScript] 접근자 프로퍼티(getter, setter)

객체 리터럴에서 접근자 프로퍼티 정의하기

객체 리터럴을 사용하여 접근자 프로퍼티를 정의할 때는 get과 set 키워드를 사용합니다. 예를 들어, 원의 반지름을 가지고 면적과 둘레를 계산하는 객체를 만들어보겠습니다.

let circle = {
  radius: 10,

  // 면적(area)을 계산하는 getter
  get area() {
    return Math.PI * this.radius ** 2;
  },

  // 둘레(circumference)를 계산하는 getter
  get circumference() {
    return 2 * Math.PI * this.radius;
  },

  // 반지름을 설정하는 setter
  set diameter(diameter) {
    this.radius = diameter / 2;
  }
};

console.log(`면적: ${circle.area}`);             // 면적: 314.159...
console.log(`둘레: ${circle.circumference}`);    // 둘레: 62.831...

circle.diameter = 20;
console.log(`새 반지름: ${circle.radius}`);      // 새 반지름: 10
console.log(`새 면적: ${circle.area}`);          // 새 면적: 314.159...

위 예제에서 area와 circumference는 값을 저장하지 않고 계산하여 반환하는 접근자 프로퍼티입니다. 반면에 diameter의 setter는 전달된 값을 사용하여 radius를 재계산합니다.

Object.defineProperty를 통한 접근자 프로퍼티 정의

이미 정의된 객체에 접근자 프로퍼티를 추가하려면 Object.defineProperty() 메서드를 사용할 수 있습니다. 이번에는 온도를 섭씨와 화씨로 변환하는 객체를 만들어보겠습니다.

let thermometer = {
  _celsius: 25, // 실제 온도는 섭씨로 저장
};

Object.defineProperty(thermometer, 'fahrenheit', {
  get() {
    return this._celsius * 9 / 5 + 32;
  },
  set(value) {
    this._celsius = (value - 32) * 5 / 9;
  },
  enumerable: true,
  configurable: true
});

console.log(`현재 섭씨: ${thermometer._celsius}°C`);    // 현재 섭씨: 25°C
console.log(`현재 화씨: ${thermometer.fahrenheit}°F`); // 현재 화씨: 77°F

thermometer.fahrenheit = 86;
console.log(`변경된 섭씨: ${thermometer._celsius}°C`); // 변경된 섭씨: 30°C

여기서 fahrenheit 프로퍼티는 접근자 프로퍼티로 정의되어, 내부적으로 _celsius 값을 기반으로 계산됩니다. 값을 설정할 때도 자동으로 섭씨로 변환되어 저장됩니다.

주의사항

한 프로퍼티에 value 또는 writable과 get 또는 set을 동시에 정의하면 오류가 발생합니다.

접근자 프로퍼티를 이용한 데이터 보호와 검증

접근자 프로퍼티는 내부 데이터에 대한 직접 접근을 제한하고, 값을 설정하거나 반환하기 전에 추가 로직을 적용할 때 유용합니다. 이번에는 은행 계좌 객체를 만들어 잔액을 안전하게 관리해보겠습니다.

let bankAccount = {
  _balance: 0, // 실제 잔액은 비공개 속성으로 관리

  get balance() {
    return this._balance;
  },

  set balance(amount) {
    if (amount < 0) {
      console.error("잔액은 음수가 될 수 없습니다.");
      return;
    }
    this._balance = amount;
  },

  deposit(amount) {
    if (amount <= 0) {
      console.error("입금액은 양수여야 합니다.");
      return;
    }
    this._balance += amount;
  },

  withdraw(amount) {
    if (amount > this._balance) {
      console.error("잔액이 부족합니다.");
      return;
    }
    this._balance -= amount;
  }
};

bankAccount.deposit(1000);
console.log(`현재 잔액: ${bankAccount.balance}원`); // 현재 잔액: 1000원

bankAccount.withdraw(500);
console.log(`인출 후 잔액: ${bankAccount.balance}원`); // 인출 후 잔액: 500원

bankAccount.balance = -100; // "잔액은 음수가 될 수 없습니다." 에러 출력
console.log(`최종 잔액: ${bankAccount.balance}원`); // 최종 잔액: 500원

위 예제에서 balance는 접근자 프로퍼티로, 직접 설정 시 음수 값이 들어가지 않도록 검증합니다. 또한 입금과 출금 메서드에서도 유효성 검사를 수행하여 데이터의 무결성을 유지합니다.

기존 코드와의 호환성을 위한 접근자 프로퍼티 활용

프로젝트가 진행되면서 객체 구조가 변경될 수 있습니다. 예를 들어, 기존에 단순히 저장했던 experience 값을 더 정교하게 계산하고 싶을 때, 내부에서는 다른 데이터(예: 입사일)를 사용하지만 외부에서는 기존의 experience 프로퍼티를 그대로 유지할 수 있습니다.

function Employee(name, joinDate) {
  this.name = name;
  this.joinDate = joinDate;

  // 기존의 experience 프로퍼티를 계산된 값으로 대체
  Object.defineProperty(this, "experience", {
    get() {
      let currentYear = new Date().getFullYear();
      let joinYear = this.joinDate.getFullYear();
      return currentYear - joinYear;
    },
    enumerable: true,
    configurable: true
  });
}

let emp = new Employee("John", new Date(2015, 0, 1));
console.log(emp.joinDate);  // 입사일 출력
console.log(emp.experience); // 현재 경력(연수) 출력