예전에 자바를 잠깐 찍먹했을 때 클래스가 '객체'라는 말을 들어봤었다. 하지만 예상과 달리 자바스크립트에서의 클래스는 '객체 지향 모델'을 위한 개념이 아니었다. 그동안 클래스 코드를 볼 때마다 헷갈리고 어려웠는데 책을 통해 공부하고 나니 그리 복잡한 개념이 아니라는 걸 알게 되었다. 클래스를 생성, 상속, 수정하는 법에 대해 정리해보았다.
MDN에서는 자바스크립트에서의 클래스 개념을 '기존 프로토타입 기반 상속에 대한 문법적 설탕(Syntactic Sugar)'라고 말한다.
모던 자바스크립트 딥 다이브 책에서 공부했던 걸 떠올려보았다.
프로토타입 기반 상속이란, 모든 객체가 갖고 있는 상위 객체인 프로토타입에 이 객체로 만들어질 인스턴스들이 공유할 상태나 메서드를 저장해놓고 상속하는 걸 의미한다.
클래스가 이것의 '문법적 설탕'이라고 얘기한 것은 아마 프로토타입 상속의 문법이 그다지 가독성이 좋지 않아서인 것 같다.
Person.prototype.greet = function() {
console.log("Hello, my name is " + this.name);
}
이렇게 'Person.prototype.greet' 식으로 세 단어 이상을 붙여쓴다. 또 메서드를 마치 줄글처럼 할당으로 선언해놓아 가독성이 영 떨어짐을 알 수 있다.
나중에 클래스 선언 코드를 보면 알겠지만 자바스크립트의 클래스를 사용하면 이러한 prototype 상속 문법을 훨씬 직관적이고 메서드답게 표현할 수 있다.
클래스를 만드는 방법에는 클래스 선언과 클래스 표현식 두 가지가 있다. 이 중에 클래스 선언 형태를 좀 더 자주 본 것 같다. 클래스 선언과 표현식은 호이스팅이 되지 않아서 선언 전에 참조하면 에러가 발생한다.
class Person {
}
함수를 변수에 담는 함수 표현식처럼 클래스를 변수에 담아 표현한다.
const person = class Person {
};
이제 실제로 클래스를 만들어보자. 클래스를 생성할 때 constructor는 한 번만 선언해야 한다.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, my name is ${this.name} and I'm ${this.age} years old`);
}
farewell() {
console.log('Good bye!');
}
}
const alberto = new Person("Alberto", 26);
alberto.greet();
// Hi, my name is Alberto and I'm 26 years old
alberto.farewell();
// Good bye!
메서드를 선언하는 부분이 prototype보다 훨씬 직관적이고 간결해진 것을 볼 수 있다.
static도 생각보다 간단한 개념이었다. static이 붙은 메서드는 클래스로 만들어진 인스턴스에서는 못 쓰고 오직 클래스로 직접 호출했을 때만 사용 가능하다.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static info() {
console.log('Hi, I'm Person class');
}
const alberto = new Person("Alberto", 26);
alberto.info();
// TypeError: alberto.info is not a function
Person.info();
// Hi, I'm Person class
}
클래스에서 세터와 게터 메서드를 선언할 수 있다. 세터는 클래스 내의 상태 값을 변경하고 게터는 클래스 내의 값을 단순히 가져오기만 할 때 사용한다.
class Person {
constructor(name, surname) {
this.name = name;
this.surname = surname;
this.nickname = nickname;
}
set nicknames(value) {
this.nickname = value; // 클래스의 일부 상태 변경
console.log(`Your new nickname is ${this.nickname}`);
}
get nicknames() {
console.log(`Your nickname is ${this.nickname}`);
}
}
const alberto = new Person("Alberto", "Montalesi");
// 세터 호출 (새로운 값을 '할당'으로 넣음)
alberto.nicknames = "Yena";
// 게터 호출 (아무 할당 없이 그냥 메서드만 적으면 실행)
alberto.nicknames;
alberto.info();
// TypeError: alberto.info is not a function
Person.info();
// Hi, I'm Person class
}
extends 키워드를 사용하여 상위 클래스를 상속받을 수 있다.
construtor에는 super를 포함해야 하는데 이때 상속받는 상위 클래스의 상태를 super의 인자로 넣는다.
메서드는 아무 처리를 하지 않아도 자동으로 상속된다.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, my name is ${this.name} and I'm ${this.age} years old`);
}
}
class Adult extends Person {
super(name, age);
this.work = work;
}
const alberto = new Adult("Alberto", 26, "software developer");
console.log(alberto.age); // 26
console.log(alberto.work); // software developer
alberto.greet(`Hi, my name is Alberto and I'm 26 years old`);
상속하는 하위 클래스에 super가 없으면 다음과 같은 에러가 뜬다.
ReferenceError: mush call super constructor before using |this|
하위 클래스에서 this를 쓰려면 꼭 super가 constructor에 필요하다는 내용이다.
첫 번째 값은 교실 이름이고 나머지는 학생 이름과 학생 점수를 나타내는 다음과 같은 클래스를 만들려면 배열(Array)을 상속받아야 한다.
class Classroom extends Array {
// rest 연산자로 교실 이름 외에 나머지 인수들을 학생들로 받는다
constructor (classname, ...students) {
// 호출할 때는 spread로 풀어서 나열된 인수 형태로 super에 인자를 넣는다
super(...students);
this.classname = classname;
}
// 새로운 학생을 추가하는 메서드
add(student) {
this.push(student);
}
}
// 만들려고 하는 클래스
const myClass = new Classroom('1A',
{ name: "Tim", mark: 6 },
{ name: "Rosy", mark: 3 },
{ name: "Jim", mark: 8 },
{ name: "Jon", mark: 10 },
);
// 새로운 학생 추가
myClass.add({ name: "Timmy", mark: 7 });
// 인덱스 4번째에 추가된 새로운 학생을 조회(get)
myClass[4]; // { name: "Timmy", mark: 7 }
// myClass에 있는 객체들 하나씩 꺼내기
for (const student of myClass) { // 여기서 myClass는 students와 동일
console.log(student);
}
// { name: "Tim", mark: 6 }
// { name: "Rosy", mark: 3 }
// { name: "Jim", mark: 8 }
// { name: "Jon", mark: 10 }
// { name: "Timmy", mark: 7 }