오늘은 자바스크립트에서 객체지향 프로그래밍으로 코드를 작성하는 방법에 대해 정리했다.
js에서 객체는 속성(properties)과 메서드(methods)를 가진 데이터 구조
ES6부터는 클래스 문법이 도입되어 객체 생성을 좀 더 직관적으로 할 수 있다.
자바랑 다르게 명시적 인터페이스나 추상 클래스는 없고 간접적으로만 구현이 가능하다.
class 키워드로 클래스 정의 - constructor 메서드에서 초기화 - 클래스 내부 메서드 정의 - new 연산자로 인스턴스 생성
// ES6 이전: 생성자 함수 사용
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`안녕하세요, 제 이름은 ${this.name}이고 ${this.age}살입니다.`);
};
}
// -------------------------------------------------------------
// ES6 이후: 클래스 문법 사용
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`안녕하세요, 제 이름은 ${this.name}이고 ${this.age}살입니다.`);
}
}
const person = new Person('김춘복', 6);
person.greet(); // 출력: 안녕하세요, 제 이름은 김춘복이고 6살입니다.
js는 프로토타입 기반 언어다.
모든 객체는 프로토타입이라는 다른 객체를 참조하며, 이를 통해 속성과 메서드를 상속받는다.
생성자 함수 정의 - 프로토타입에 메서드 추가 - new 연산자로 인스턴스 생성
프로토타입 체인
객체가 속성이나 메서드를 찾을 때 자신의 프로토타입을 따라 상위 프로토타입으로 연결되는 구조로, 이를 통해 여러 단계의 상속이 가능하다.
(자바는 단일 상속만 지원)
// 프로토타입 체인 예제
const animal = {
eat: function() {
console.log('먹는 중...');
}
};
const dog = Object.create(animal);
dog.bark = function() {
console.log('멍멍!');
};
const puppy = Object.create(dog);
puppy.play = function() {
console.log('놀고 있어요!');
};
puppy.eat(); // 출력: 먹는 중... (animal로부터 상속)
puppy.bark(); // 출력: 멍멍! (dog로부터 상속)
puppy.play(); // 출력: 놀고 있어요! (자체 메서드)
데이터와 메서드를 하나의 단위로 묶어서 외부에서 접근할 수 없도록 보호하는 개념
js에서는 자바처럼 접근제어자로 완전한 캡슐화는 지원하지 않았지만, 클로저나 심볼을 이용해 구현이 가능했다.
class Person {
constructor(name, age) {
// 클로저를 활용한 프라이빗 변수
const _name = name;
const _age = age;
// 게터 메서드
this.getName = function() {
return _name;
};
this.getAge = function() {
return _age;
};
// 세터 메서드
this.setName = function(newName) {
_name = newName;
};
this.setAge = function(newAge) {
if (newAge > 0) {
_age = newAge;
}
};
}
// 공개 메서드
introduce() {
console.log(`안녕하세요, 제 이름은 ${this.getName()}이고, ${this.getAge()}살입니다.`);
}
}
const person = new Person('홍길동', 30);
person.introduce(); // "안녕하세요, 제 이름은 홍길동이고, 30살입니다."
console.log(person._name); // undefined - 직접 접근 불가
class User {
#email; // 프라이빗 필드 선언
#password;
constructor(email, password) {
this.#email = email;
this.#password = password;
}
getEmail() {
return this.#email;
}
setEmail(newEmail) {
if (newEmail.includes('@')) {
this.#email = newEmail;
} else {
throw new Error('유효하지 않은 이메일 형식입니다.');
}
}
#validatePassword(password) {
return password.length >= 8;
}
changePassword(oldPassword, newPassword) {
if (oldPassword === this.#password && this.#validatePassword(newPassword)) {
this.#password = newPassword;
return true;
}
return false;
}
}
const user = new User('user@example.com', 'password123');
console.log(user.getEmail()); // 'user@example.com'
// console.log(user.#email); // SyntaxError: Private field '#email' must be declared in an enclosing class
한 클래스가 다른 클래스의 속성과 메서드를 물려받는 개념
js에서는 프로토타입 기반 상속과 클래스기반 상속 모두 지원
// 클래스 기반 상속
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}이/가 소리를 냅니다.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 부모 클래스의 생성자 호출
this.breed = breed;
}
speak() {
console.log(`${this.name}이/가 멍멍 소리를 냅니다.`);
}
fetch() {
console.log(`${this.name}이/가 물건을 가져옵니다.`);
}
}
const dog = new Dog('멍멍이', '골든 리트리버');
dog.speak(); // 출력: 멍멍이이/가 멍멍 소리를 냅니다.
dog.fetch(); // 출력: 멍멍이이/가 물건을 가져옵니다.
동일한 인터페이스를 통해 다양한 객체 타입에 접근할 수 있는 능력
js에서는 메서드 오버라이딩이 그 예시
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}이/가 소리를 냅니다.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name}이/가 멍멍 소리를 냅니다.`);
}
}
class Cat extends Animal {
speak() {
console.log(`${this.name}이/가 야옹 소리를 냅니다.`);
}
}
// 다형성 활용
function makeAnimalSpeak(animal) {
animal.speak();
}
const dog = new Dog('멍멍이');
const cat = new Cat('야옹이');
makeAnimalSpeak(dog); // 출력: 멍멍이이/가 멍멍 소리를 냅니다.
makeAnimalSpeak(cat); // 출력: 야옹이이/가 야옹 소리를 냅니다.