이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.
자바스크립트는 프로토타입 기반 객체지향 언어이다. 따라서 클래스가 필요없는 객체 지향 스타일로 타 객체 지향 언어의 상속, 캡슐화 등의 개념을 구현할 수 있었다. ES5에서는 아래 코드와 같이 클래스를 구현하였다.
ES6에 들어서면서 타 객체 지향 언어와 비슷하게 클래스를 지원하였다. 기존의 프로토타입 기반의 객체 지향 모델을 폐지한 것은 아니며, 그렇다고 새로운 객체 지향 모델을 제공하는 것 또한 아니다. 클래스도 함수이며, 일각에서는 기존의 프로토타입 기반 패턴의 문법적 설탕(Syntactic Sugar)이라고 보기도 한다. 하지만 클래스가 생성자 함수와 동일하게 동작하지 않으며 더 엄격하다는 점에서 차이를 보인다.
ES5에서는 생성자 함수와 프로토타입, 클로저를 활용하여 다음과 같이 객체 지향 프로그래밍을 구현하였다.
var Fruit = (function() {
// constructor
function Fruit(price) {
this._price = price;
}
// public static method
Fruit.helloWord = function () {
console.log('Hello, World!');
}
// public instance method
Fruit.prototype.getPrice = function () {
console.log('Price : ' + this._price);
}
// return constructor
return Fruit;
}());
var apple = new Fruit(1000);
Fruit.helloWord(); // Hello, World!
apple.getPrice(); // Price : 1000
console.log(apple instanceof Fruit); // true
// 인스턴스 추가 생성
// var banana = new Fruit(2000);
위 코드를 그림으로 정리하면 다음과 같이 나타낼 수 있다.
ES6 클래스는 class
키워드를 사용하여 정의한다. 생성자 함수와 마찬가지로 파스칼 케이스를 사용하는 것이 일반적이나, 그렇지 않아도 에러는 발생하지 않는다.
class Fruit {}
class fruit {} // Non Error
인스턴스 생성은 new
연산자와 함께 클래스 이름을 호출하면 된다.
class Example {}
const exam = new Example();
기존에는 클래스 몸체에는 메소드만 선언이 가능했으며 필드(멤버 변수)를 선언하면 SyntaxError
가 발생했다. 하지만, Chrome 72
또는 Node 12
이상의 버전에서 동작하면 정상적으로 실행된다. 또한 #
기호를 통해 private
접근제한자를 설정해줄 수 있다.
// Chrome 72 또는 Node 12 이상의 환경
class Hello {
hi = 'hi'; // public field
#hello = 'hello'; // private Field
static bye = 'bye'; // public static field
static #goodBye = 'good bye'; // private static field
// ...
}
const hello = new Hello();
console.log(hello.hi); // hi
console.log(hello.#hello); // SyntaxError: Private field '#hello' must be declared in...
console.log(Hello.bye); // bye
console.log(Hello.#goodBye); // SyntaxError: Private field '#goodBye' must be declared in...
인스턴스를 생성하고 클래스 필드를 초기화하기 위한 특수한 메소드이다.
class Fruit {
name;
constructor(name) {
this.name = name;
}
}
const apple = new Fruit('apple');
console.log(apple.name) // apple
메서드는 크게 인스턴스 메서드와 정적 메서드로 나눌 수 있다.
소목차 ES5에서 OOP에서 사용한 예제 코드를 ES6 Class 문법으로 재구현 해보자.
class Fruit {
price;
constructor(price) {
this.price = price;
}
static helloWord() {
console.log('Hello, World');
}
getPrice() {
console.log(`Price : ${this.price}`);
}
}
const apple = new Fruit(1000);
Fruit.helloWord(); // Hello, World
apple.getPrice(); // Price : 1000
console.log(apple instanceof Fruit); // true
그림으로 나타내면 다음과 같다.
객체 지향 프로그래밍(OOP)의 다형성과 관련이 깊은 내용으로, 새롭게 정의할 클래스가 기존의 클래스와 유사하다면 상속을 통해 다른 점만 구현하면 된다. 코드 재사용에 있어서 유용하다.
부모 클래스(Base Class)로부터 상속받아 자식 클래스(Sub Class)를 정의할 때 사용한다.
부모 클래스를 참조할 때 또는 부모 클래스를 호출할 때 사용한다. 자식 클래스의 constructor
내부에서 super
메서드를 호출하지 않으면 this
참조 에러가 발생한다.
다음 코드를 보자.
class Fruit {
name;
static type = 'Fruit';
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
static foodType() {
return Fruit.type;
}
}
class Apple extends Fruit {
taste;
static shape = 'Circle';
constructor(name, taste) {
super(name);
this.taste = taste;
}
getTaste() {
return this.taste;
}
static foodShapeType() {
return `${Apple.shape} in ${super.foodType()}`;
}
// 자식 클래스의 인스턴스는 부모 클래스의 정적 메서드 참조 불가 (프로토타입 체인 상이)
// getFoodType() {
// return `${super.foodType()}`;
// }
}
const apple = new Apple('apple', 'sweet');
console.log(apple instanceof Fruit); // true
console.log(apple.getTaste()); // sweet
console.log(apple.getName()); // apple
console.log(Apple.foodShapeType()); // Circle in Fruit
console.log(Apple.foodType()); // Fruit
// console.log(apple.getFoodType()); // TypeError: (intermediate value).foodType is not a function
이를 그림으로 나타내면 다음과 같다.
자바스크립트에서 오버라이딩은 지원하나, 오버로딩은 지원하지 않는다. 다만, arguments
객체를 활용하여 구현할 수는 있다.
get
: getter, 반드시 무언가를 반환하는 것이 좋다.set
: setter, 필드 값 할당 시 사용한다.이 둘은 필드 이름처럼 사용되는 것에 주의하자.
class Fruit {
name;
constructor(name) {
this.name = name;
}
set changeFruitName(newName) {
this.name = newName
}
get nameOfFruit() {
return this.name;
}
}
const banana = new Fruit('banana');
banana.changeFruitName = 'choco banana';
console.log(banana.nameOfFruit); // choco banana