Class에 대해 공부하는 도중, getter와 setter가 왜 필요하고 어떻게 동작하는지 이해가 되지 않았다. 그래서 이번 기회에 정리해보고자 한다.
getter
- 객체의 값을 가져올 때(읽을 때) 자동으로 호출되는 메서드이다.
- get 키워드로 정의하며, 별도의 인자를 받지 않는다.
- 내부적으로는 함수지만, 외부에서 접근할 때는 obj.prop와 같이 프로퍼티처럼 보인다.
setter
- 객체의 값을 설정할 때 자동으로 호출되는 메서드이다.
- set 키워드로 정의하며, 주로 하나의 인자를 받는다.
- 외부에서 obj.prop = 값 형태로 할당하면, 내부적으로 set prop(값)이 실행된다.
예시
다음과 같이 Person 클래스가 있다. name 프로퍼티를 게터·세터로 관리해, 이름을 등록하거나(set) 가져온다(get).
class Person { constructor(name) { this._name = name; } // getter get name() { console.log('getter 호출'); return this._name; } // setter set name(newName) { console.log('setter 호출'); this._name = newName; } } const person = new Person('Alice'); // getter 작동 console.log(person.name); // 콘솔 출력> // getter 호출 // Alice // setter 작동 person.name = 'Bob'; // 콘솔 출력> // setter 호출 console.log(person.name); // 콘솔 출력> // getter 호출 // Bob
person.name으로 값을 읽을 때, get name()이 자동으로 호출되어 this._name을 반환한다.
person.name = 'Bob'으로 값을 할당하면, set name(newName)이 실행되어 내부 _name 값을 업데이트한다.
getter와 setter를 사용하는 이유
클래스 문법을 배울 때, 보통 생성자(constructor)와 메서드만 알아도 객체를 생성하고 사용하는 데 큰 어려움이 없어 보일 수 있다. 그래서 'getter와 setter를 꼭 써야 하나?'라는 의문이 들 수도 있다.
하지만 캡슐화(encapsulation) 관점에서 보면, 객체 내부 속성을 직접 수정하는 것은 위험할 수 있다.
예를 들어, 나이가 음수가 되지 않도록 강제하거나, 이름이 특정 형식을 따라야 할 때, 게터·세터를 통해 검증 로직을 넣으면 원치 않는 결과를 예방할 수 있다. 이는 객체 지향 프로그래밍(OOP)에서 중요한 개념이기도 하다.
아래 예시에서는 Person 클래스에 출생 연도를 저장해두고, 나이를 동적으로 계산해서 반환하는 게터가 있다. 하지만 세터가 없으므로 외부에서 나이를 임의로 바꾸는 것이 불가능하다.
class Person { constructor(name, birthYear) { this._name = name; this._birthYear = birthYear; } // 출생 연도를 이용해, 현재 나이를 계산해 반환 get age() { const currentYear = new Date().getFullYear(); return currentYear - this._birthYear; } } const person2 = new Person('Tom', 2000); console.log(person2.age); // 25 // 세터가 없으므로 외부에서 age에 임의 할당 불가 person2.age = 99; console.log(person2.age); // 여전히 25
사용 시 주의사항
1) 게터/세터 내부에서 자기 자신(this.name)을 부르면 무한 재귀가 발생한다
class Person { constructor(name) { this.name = name; // -> set name() 호출 } get name() { // ❌ 잘못된 사용: 내부에서 this.name 호출 return this.name; // 다시 get name() 호출 -> 무한 재귀 } set name(value) { // ❌ 잘못된 사용: this.name으로 재할당 -> 무한 재귀 this.name = value; } }
게터 안에서 this.name을 사용하면 “프로퍼티 name에 접근 → 게터 재호출”로 이어져 무한 루프에 빠진다.
세터 안에서도 마찬가지로 this.name = value를 쓰면 같은 세터를 재호출하게 되어 역시 무한 루프에 빠진다.
2) 내부에서는 _name(또는 #name) 같은 별도 프로퍼티로 접근해야 한다
class Person { constructor(name) { // 여기서도 set name()이 호출되지만, // 세터 내부에서는 _name에 값을 할당하므로 안전 this.name = name; } get name() { return this._name; } set name(value) { this._name = value; } }
이렇게 하면 게터·세터가 서로를 재귀 호출하지 않고, 안정적으로 내부 값을 관리할 수 있다.
get, set의 내부 변수에서 언더스코어( _ )를 붙이는 이유
처음에는 '왜 _name처럼 언더스코어를 쓰는가?' 의문이 들 수 있다. 사실 강제 규칙이 아니므로, 꼭 붙일 필요는 없다.
다만 ‘내부용 필드’임을 명시적으로 보여주어 재귀적 호출 실수를 예방하고, 코드를 읽는 사람이 '이건 내부용 프로퍼티구나~'라는 사실을 쉽게 파악할 수 있다는 장점이 있다.
또한, private field(#name) 문법을 사용하면 클래스 외부에서 접근을 완전히 차단할 수도 있다.
팀 규칙이나 상황에 따라 언더스코어 또는 # 필드를 쓰는 식으로 스타일이 달라질 수 있다.
get, set 메서드 이름 규칙
처음에는 this.name = name으로 정의했는데, 게터·세터 내부에서는 this._name 등 실제 내부 변수명과 다를 수 있다.
그래도 문제가 없는 이유는 게터/세터 이름이 곧 '외부에서 보이는 프로퍼티 이름'이기 때문에 가능한거다.
즉, 내부에서는 _name이든 _banana든 어떤 변수명을 사용해도, 게터/세터가 정의된 이름이 name이면, person.name으로 접근할 때 그 게터·세터가 자동 호출된다.
아래 예시에서 게터·세터 이름은 age이지만, 내부에는 _banana라는 프로퍼티를 사용하고 있다.
하지만 콘솔로 출력해보면 문제 없이 30이 출력되는걸 확인할 수 있다.
class Person { constructor(age) { // 인스턴스 생성 시, 내부적으로 set age(...)가 호출됨 this.age = age; } get age() { return this._banana; } set age(value) { this._banana = value; } } const p = new Person(30); console.log(p.age); // 내부적으로 get age() 실행 -> this._banana 반환 // 콘솔 출력 > 30
참고
코어 자바스크립트 | 프로퍼티 getter와 setter
Youtube | 드림코딩 - 자바스크립트 6. 클래스와 오브젝트의 차이점(class vs object), 객체지향 언어 클래스 정리
'JavaScript' 카테고리의 다른 글
JavaScript에서 Number의 큰 수 처리 한계, BigInt로 해결 (2) | 2024.11.28 |
---|---|
for문 내에 변수를 선언해도 괜찮을까? (1) | 2024.11.24 |
데이터 타입은 왜 필요한가? 동적 타입 언어의 특징도 알아보자 (0) | 2024.08.02 |
JavaScript 원시 타입 종류 7가지 (0) | 2024.08.02 |
변수는 왜 필요한가?, 어떻게 선언되고 할당되는지도 알아보자 (feat. 메모리) (0) | 2024.07.31 |