자바스크립트 객체 지향 프로그래밍 시리즈 대망의 마지막 포스팅이다. 사실 마지막 포스팅에 도달하고 나서야 조금 마음이 놓인다. 아무래도 기존에 클래스 기반 객체 지향 프로그래밍을 사용한 경험이 있던 탓에, 생소한 Prototype 문법보다는 클래스 문법이 훨씬 익숙하기 때문이다.
자바스크립트 언어를 사용하는 많은 개발자 분들이 나와 같이 prototype 문법이 매우 난잡하다고 생각했기 때문인지는 모르겠지만, 자바스크립트 버전 ES6 이후부터 본격적으로 클래스 문법을 지원하기 시작했다. 물론, 클래스 문법을 지원한다고 해도 결국 밑바닥에는 기본 설계 구조인 Prototype을 사용하기 때문에, 클래스 문법을 사용한다고 하더라도 동작 방식은 이전 포스팅에서 설명한 것과 동일하다고 생각하면 된다. 물론, 우리에게는 내부적으로 어떻게 동작하던 간에 성능이 똑같다면 사용하기 편한게 제일이지만 말이다.
서론이 길었다. 아마 여태까지의 포스팅들 중 가장 짧은 포스팅이 될 것 같다는 노파심에 나도 모르게 서론을 길게 써버렸다. 이제 본격적으로 클래스 문법을 사용하는 ES6 자바스크립트 객체 지향 프로그래밍에 대해 알아보자.
클래스라는 개념이 없어 함수로 클래스를 대신해야 했던 이전 자바스크립트와는 달리, ES6부터는 Car라는 클래스를 선언하고 싶다면 간단하게 다음과 같이 class 키워드를 사용하여 코드를 작성하면 된다.
class Car {
constructor(name, speed) {
this.name = name;
this.speed = speed;
}
Drive() {
return this.speed * 60;
}
}
this 키워드를 사용해 클래스 속성을 정의하는 것은 이미 익숙하실 것이다. 그리고 단지 Prototype 객체에서 기존 함수를 가리키는 용도로 사용되었던 ES6 이전의 constructor(생성자)에 비해, 클래스 문법에서 constructor는 객체 생성 시 인자를 받아 속성 값에 할당해주는 중요한 역할을 담당하도록 변경되었다.
또한, 번거롭게 Prototype 객체에 메서드를 정의해야 했던 이전 문법과는 달리, 클래스 안에서 간단하게 메서드를 정의할 수 있다. 다만 주의해야 할 점은, 함수라고 해서 앞에 function 키워드를 붙여서는 안된다는 것이다. constructor가 함수임에도 앞에 function 키워드를 사용하지 않는 것에서 볼 수 있듯이, function 키워드를 사용하면 에러가 발생한다. 이는 Arrow Function 문법을 사용할 때에도 마찬가지로, 아래와 같이 코드를 작성하면 에러가 발생한다.
class Car {
constructor(name, speed) {
this.name = name;
this.speed = speed;
}
const Drive = () => { // 에러 발생
return this.speed * 60;
}
}
따라서 Arrow Function 문법을 사용할 때에도 함수 앞에 const나 let, 또는 var을 사용하지 말아야 한다.
객체 생성은 ES6 이전 문법과 다를게 없으니 간단히 코드만 언급하고 넘어가겠다. new 키워드를 사용하면 된다.
const myCar = new Car("Tico", 132);
ES6 이전 문법에서 상속을 구현하는 것은 매우 고된 일이었지만, ES6 이후의 자바스크립트에서는 extends 키워드 하나로 해결된다. 만약 Benz 클래스가 위와 같은 Car 클래스를 상속받고자 한다면 다음과 같이 extends 키워드를 사용하기만 하면 된다.
class Benz extends Car {
}
단지 Car 클래스를 상속받기만 할 것이라면 Benz 클래스 내부에 어떤 내용도 추가하지 않은 위 코드로 충분하다. Benz 클래스가 Car 클래스를 상속받으면 constructor도 상속받게 되고, Benz 클래스에서 명시적으로 constructor 함수를 다시 작성하지 않는 한 Benz 객체 생성 시 상속받은 Car 클래스의 constructor가 호출되기 때문이다.
만약 Car 클래스의 속성인 name 및 speed에 추가로 Benz만의 속성인 id를 추가하고 싶다면 상속받은 constructor 함수를 재정의해야 한다. 이 때 재정의할 Benz 클래스의 constructor 함수 가장 위에 부모 클래스 constructor 호출을 의미하는 super()를 사용한 후, 아래에 Benz 클래스에 추가할 속성들을 명시해주면 된다. 참고로 super는 객체 지향 프로그래밍에서 주로 부모를 의미하는 단어이다.
class Benz extends Car {
constructor(name, speed, id) {
super(name, speed);
this.id = id;
}
}
메서드 오버라이딩이란 상속 관계에 있는 부모 클래스에 정의된 메서드를 자식 클래스에서 재정의하는 것을 뜻한다. 예를 들어, 우리가 Benz 클래스가 Car 클래스로부터 상속받은 Drive 메서드에 대해 별도의 작업을 하지 않으면, Benz 타입 객체들은 Drive 메서드 호출 시 Car 클래스로부터 상속받은 Drive 메서드를 그대로 사용하게 된다.
하지만, 만약 Benz 클래스에서 Car 클래스가 제공하는 Drive 메서드 기능에 추가로 Benz 클래스만의 기능을 추가하고 싶다면 상속 받은 Drive 메서드를 재정의할 수 있는데, 이를 메서드 오버라이딩이라고 한다. 이 때 메서드 오버라이딩이라고 부르려면 재정의한 Benz 클래스의 Drive 함수가 Car 클래스의 Drive 함수와 동일한 매개변수를 받아야 하며, 값을 리턴한다면 리턴하는 값의 타입도 동일해야 한다.
Drive 메서드를 메서드 오버라이딩으로 재정의할 때 상속 개념을 유지하려면 Car 클래스의 Drive 메서드 기능을 그대로 살린 채 기능을 추가해야 하는데, 이 때 Benz 클래스의 Drive 메서드에서 부모 클래스인 Car의 Drive 메서드를 호출하는 것을 가능하게 만드는 키워드가 바로 super이다. super 키워드는 부모 클래스가 가지고 있는 함수들을 자식 클래스에서 호출하기 위해 사용되며, 아래와 같이 super.Drive()
를 통해 Benz 클래스에서 Car 클래스의 Drive 메서드를 호출할 수 있다.
class Benz extends Car {
constructor(name, speed, id) {
super(name, speed);
this.id = id;
}
Drive() {
const result = super.Drive();
return result + 100;
}
}
사실 이 포스팅의 마지막 부분에, 자바스크립트 클래스에서 private 속성을 정의할 수 있도록 ES2019에서 추가된 해쉬(#) prefix에 대한 내용을 추가하려고 했다. 하지만 개인적으로 검색해보니 정식으로 도입된지 얼마 되지 않은 기능이라 실제 프로젝트에 적용해 본 사람도 많지 않고, 문제점이 있다고 지적하는 글도 봤기 때문에 괜히 독자분들께 혼란을 주고 싶지 않아 제외했다.
ES6 클래스 문법을 끝으로 길고 긴 자바스크립트 객체 지향 프로그래밍에 대한 시리즈가 마무리되었다. 사실 앞쪽의 개념 부분 포스팅에 힘을 너무 쏟은 나머지 뒷부분 포스팅 내용이 많이 부실해진 것 같다. 그래도 필자가 작성한 시리즈로 인해 자바스크립트에서 객체 지향 프로그래밍이 어떻게 동작하는지에 대한 대략적인 이해라도 얻어가셨다면 성공적인 포스팅이었다고 생각한다.
시리즈를 쭉 읽으시면서 이해가 가지 않는 점이나 틀린 부분이 있다면 댓글로 언제든지 지적해주시면 감사하겠다. 필자는 언제나 나 자신이 틀릴 수 있다는 것을 항상 유념해두고 있기 때문에, 독자분들의 지적을 적극 반영할 준비가 되어있다.
마지막으로 본 시리즈를 끝까지 읽어주신 독자분들께 감사하다는 말씀을 올리며, 자바스크립트 객체 지향 프로그래밍 시리즈를 마무리하겠다.
좋은 포스팅 감사합니다!! 이번 시리즈를 통해서 JS 의 전반적인 OOP 개념을 충분히 잡을 수 있었습니다!!