
JavaScript의 클래스(Class)에 대해 알아보자.
자바스크립트에서 클래스는 함수의 한 종류로, 동일한 종류의 객체를 여러 개 생성해야 할 때 사용하는 문법이다.
모던 자바스크립트에 도입된 클래스(class)라는 문법을 사용하면 객체 지향 프로그래밍에서 사용되는 다양한 기능을 자바스크립트에서도 사용할 수 있다.
기본적인 문법은 아래와 같다.
// Class 선언
class MyClass {
// 여러 메서드를 정의할 수 있음
// 생성자 메서드 - new 키워드를 사용하면 호출
constructor() {
// 객체의 기본 상태를 설정
...
}
// 사용할 메서드
method1() { ... }
method2() { ... }
...
}
Class 라는 예약어를 이용하여 객체를 만들기 위한 틀을 생성할 수 있는데, 이를 Class 선언이라고 한다.
// Class 선언
class Person {
// 여러 메서드를 정의할 수 있음
// 생성자 메서드 - new 키워드를 사용하면 호출
constructor(name) {
// 객체의 기본 상태를 설정
this.name = name;
}
}
만들어진 Class를 이용하여 인스턴스 객체를 만들 수 있다.
만약 Class 내부에 메서드가 있다면 메서드를 사용할 수 있다.
// 인스턴스 만들기
const user1 = new Person('hong'); // 생성자 호출
Class 내부에 함수를 메서드 형식으로 정의할 수 있다.
메서드는 객체가 가진 동작(기능)을 표현한다.
// Class 선언
class Person {
constructor(name) {
this.name = name;
}
// 메서드 정의
sayHi() {
return `${this.name}: 안녕!`;
}
}
메서드 내부에서 this.name은 해당 객체의 name 속성을 가리킨다.
new 연산자를 통해 생성한 인스턴스 객체를 이용하여 내부 메서드를 사용할 수 있다.
생성된 객체에서 메서드를 사용하면 this는 그 객체 자신을 가리킨다.
const user1 = new Person('hong');
user1.sayHi(); // 메서드 사용
this는 메서드가 누구를 기준으로 실행되는지 결정한다.
클래스 안에서 this를 사용하면 this는 해당 인스턴스 객체를 의미한다.
const user1 = new Person("hong");
const user2 = new Person("gang");
console.log(user1.sayHi()); // "hong: 안녕!"
console.log(user2.sayHi()); // "gang: 안녕!"
위의 내용을 합하여 예제를 만들어보자.
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
return `${this.name}: 안녕!`;
}
}
const user1 = new Person("hong");
const user2 = new Person("gang");
const user3 = new Person("lee");
console.log(user1.name); // "hong"
console.log(user2.name); // "gang"
console.log(user3.name); // "lee"
console.log(user1.sayHi()); // "hong: 안녕!"
console.log(user2.sayHi()); // "gang: 안녕!"
console.log(user3.sayHi()); // "lee: 안녕!"
클래스는 다른 클래스를 확장(상속)하여 재사용 할 수 있다.
A 클래스를 상속받은 B 클래스는 A 클래스의 기능을 모두 물려받으며,
물려받은 기능을 그대로 사용하거나 수정해서 사용(오버라이팅)할 수 있고, B 클래스만의 새로운 기능을 추가할 수도 있다.
상속을 받기 위해서는 extends 키워드를 사용해야 한다.
class Animal {
constructor(name) {
this.name = name;
}
run() {
console.log(`${this.name}이(가) 달림`);
}
speak() {
console.log(`${this.name}이(가) 소리를 냄`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name}이(가) 강아지 소리를 냄`);
}
bark() {
console.log(`${this.name}이(가) 멍멍!`);
}
}
// 인스턴스 객체 생성
const puppy = new Dog("초코");
// 사용
puppy.run(); // "초코이(가) 달림"
puppy.speak(); // "초코이(가) 강아지 소리를 냄"
puppy.bark(); // "초코이(가) 멍멍!"
위의 예제에서 Dog 클래스는 Animal 클래스를 상속받는다.
Dog 클래스는 Animal 클래스가 가지고 있는 name 속성과 speak() 메서드를 그대로 사용할 수 있다.
또한 bark()라는 메서드를 추가로 생성하여 사용한다.
상속받은 클래스에서 constructor()를 쓰려면 constructor() 내부에서 가장 먼저 super()를 호출해야 한다.
이때 super()는 상속받는 클래스의 constructor()를 호출하는 역할을 한다.
class Cat extends Animal {
constructor(name, color) {
super(name); // 부모의 constructor 호출
this.color = color;
}
introduce() {
console.log(`${this.color} 고양이 ${this.name}`);
}
}
const kitty = new Cat("나비", "회색");
kitty.speak(); // "나비이(가) 소리를 냄"
kitty.introduce(); // "회색 고양이 나비"
메서드에서 오버라이딩을 구현하고 싶을 때도 super를 사용할 수 있다.
class Cat extends Animal {
constructor(name, color) {
super(name); // 부모의 constructor 호출
this.color = color;
}
run() {
super.run();
console.log(`고양이의 날쎈 모습!`);
}
introduce() {
console.log(`${this.color} 고양이 ${this.name}`);
}
}
const kitty = new Cat("나비", "회색");
kitty.run(); // "나비이(가) 달림 고양이의 날쎈 모습!"
constructor()에서 모두 설정하는 기존 방식 대신 클래스 안에서 속성(필드)을 바로 선언할 수 있다.
인스턴스를 만들면 자동으로 작성해둔 값이 들어간다.
class Person {
name = "이름없음";
sayName() {
console.log(`이름: ${this.name}`);
}
}
const user1 = new Person();
user1.sayName(); // "이름: 이름없음"
Class 외부에서 직접 접근할 수 없는 값을 만들고 싶을 때 필드명 앞에 #을 붙인다.
해당 값을 접근할 수 있는 메서드(getter/setter)를 만들어 사용할 수 있다.
class User {
#password = "1234";
checkPassword(input) {
return input === this.#password;
}
}
const user = new User();
console.log(user.#password); // ❌ 에러 발생
console.log(user.checkPassword("1234")); // true
#password 값은 외부에서 직접적으로 접근할 수 없으며, 수정할 수 없다.
필요한 경우 메서드를 통해 값에 접근할 수 있다.
#을 붙여서 만든 private 필드에 접근하기 위해서는 반드시 getter/setter 메서드를 사용해야 한다.
값을 불러오는 메서드를 getter, 값을 수정하는 메서드를 setter라고 한다.
class User {
#password = "1234";
// getter
getPassword() {
return this.#password;
}
// setter
setPassword(pw) {
this.#password = pw;
}
}
const user = new User();
console.log(user.getPassword()); // "1234"
console.log(user.setPassword("4321"));
console.log(user.getPassword()); // "4321"
get/set 키워드를 이용하면 마치 객체의 프로퍼티에 접근하듯이 값을 다룰 수 있다.
class User {
#password = "1234";
// getter
get password() {
return this.#password;
}
// setter
set password(pw) {
this.#password = pw;
}
}
const user = new User();
console.log(user.password()); // "1234"
user.password = "4321";
console.log(user.password()); // "4321"
get/set 키워드 사용시 메서드가 실행되는 것이 아닌 프로퍼티를 수정하는 것으로 오해를 불러일으킬 수 있다.
협업시에는 주석이나 가이드 문서를 이용하여 충분한 정보를 제공하는 것이 좋다.
또한 private 필드 선언을 한 프로퍼티의 경우 비공개임에도 불구하고 일반 프로퍼티와 같이 접근이 가능하게 되니 주의해야 한다.
#이 나오기 전에는 언더바(_)를 사용했다.
이 때 언더바(_)는 읽기 전용으로 사용하겠다는 표시로, 개발자간의 암묵적인 규약이었다.
JavaScript 자체의 강제성이 아니기 때문에 실제로는 수정이 가능한 변수이다.
정적 메서드와 정적 필드에 대해 알아보자.
static 키워드는 클래스 자체에 속하는 속성(필드)이나 메서드를 만들 때 사용한다.
속성(필드)이나 메서드의 앞에 static 키워드를 작성하면 된다.
공통 도구 함수(유틸리티)를 만들거나 인스턴스 없이 사용하는 전역 카운터 등을 만들 때 사용한다.
혹은 데이터 저장용 공간을 클래스 자체에 붙이고 싶은 경우에 사용할 수도 있다.
new 없이 클래스 이름으로 바로 호출해서 사용할 수 있다.
class MathHelper {
static add(x, y) {
return x + y;
}
}
console.log(MathHelper.add(2, 3)); // 5
static 키워드로 만들어진 값은 클래스 자체에 붙어있기 때문에 인스턴스 객체에서는 접근할 수 없다.
const helper = new MathHelper();
console.log(helper.add); // undefined
static 키워드를 필드에 붙이면 해당 필드는 외부에서 접근이 가능하다.
클래스 전체에 하나만 존재하는 값으로 인스턴스 객체 공유가 되지 않는다.
class Counter {
static count = 0;
static increase() {
this.count++;
}
}
Counter.increase();
Counter.increase();
console.log(Counter.count); // 2