JS는 프로토타입 기반 언어라 “상속” 개념이 없어서 클래스를 흉내내는 기법들이 생겨났다.
이러한 요구로 ES6에서 클래스 문법이 나왔다.
💡 클래스: 사물의 공통 속성을 모아 정의한 추상적인 개념으로 상위 클래스, 하위 클래스로 나눌 수 있다.
음식범주 안에 고기,채소,과일 등이 들어간다. 과일 안에는 사과,귤 등이 있다. 여기서 사과, 귤 등은 실존하는 사물인 반면 음식, 과일은 사물의 공통 속성을 모아 정의한 추상적인 개념이다.
한편 음식은 과일보다 상위(superior)개념, 과일은 음식보다 하위(subordinate)개념이다.
상위 클래스:
superclass
, 하위 클래스:subclass
클래스는 하위로 갈수록 상위 클래스의 속성을 상속하며 구체적인 요건이 추가, 변경된다. 하지만 클래스는 결국 추상적 개념일 뿐이다.
💡 인스턴스: 클래스의 속성을 지니는 실존하는 개체, 클래스의 구체적인 예시이다.
클래스의 속성을 지니는 실존하는 개체를 “인스턴스”라고 한다. 어떤 클래스에 속한 객체는 그 클래스의 조건을 모두 만족하므로 그 클래스의 구체적인 인스턴스(예시)가 된다.
인스턴스는 하나의 클래스를 바탕으로 만들어진다. 인스턴스가 다양한 클래스에 속할 수는 있지만 인스턴스 입장에서 클래스들은 ‘직계존속’이다. 왜냐하면 인스턴스 생성시 호출 가능 클래스는 하나이기 때문이다.
생성자 함수 Array를 new 연산자와 같이 호출하면 인스턴스가 생성된다.
Array를 클래스라 한다면 Array의 prototype 내부 요소들이 인스턴스에 ‘상속’되고 나머지는 상속되지 않는다.
(실제론 상속이 아닌 프로토타입 체이닝에 의한 참조지만 동일하게 동작한다.)
인스턴스에 상속되는지(인스턴스가 참조하는지)에 따라 스태틱 멤버, 인스턴스 멤버로 나뉜다.
JS에선 인스턴스에 메서드를 정의할 수 있어 인스턴스 메서드가 인스턴스, 프로토타입 어디에 정의한 메서드인지 헷갈리기 때문에 프로토타입 메서드라고 부르는 것이 좋다.
var Rectangle = function(width, height) {
this.width = width;
this.height = height;
};
Rectangle.prototype.getArea = function() { //(프로토타입)메서드
return this.width * this.height;
};
Rectangle.isRectangle = function(instance) { // 스태틱 메서드
return instance instanceof Rectangle &&
instance.width > 0 && instance.height > 0;
};
var rect1 = new Rectangle(3,4);
console.log(rect1.getArea()); //(1번) 12
console.log(rect1.isRectangle()); //(2번) Error: rect1.isRectangle is not a function
console.log(Rectangle.isRectangle(rect1)); //(3번) true
1번 콘솔처럼 인스턴스에 직접 호출할 수 있는 메서드가 프로토타입 메서드다.
2번 콘솔처럼 인스턴스에 직접 접근할 수 없는 메서드를 스태틱 메서드라 한다. 스태틱 메서드는 3번 콘솔처럼 생성자 함수를 this로해야 호출 가능하다.
클래스는 사용하기에 따라 추상적일 수도 구체적 개체가 될 수 있다.
구체적인 인스턴스가 사용할 메서드를 정의한 ‘틀’의 역할을 담당하는 목적을 가질 때의 클래스는 추상적인 개념이지만, 클래스 자체를 this로 해서 직접 접근해야만 하는 스태틱 메서드를 호출할 때의 클래스는 그 자체가 하나의 개체로서 취급된다.
이번절은 프로토타입 체인을 활용한 클래스 상속 구현과 전통적인 객체지향 언어에서의 클래스와 비슷한 형태로 발전시키는 것을 목표로 한다.
JS에서 클래스 상속은 프로토타입 체이닝을 잘 연결한 것으로 이해하면 된다.
클래스에 있는 값이 인스턴스 동작에 영향을 줘선 안된다. 이는 클래스의 추상성을 해친다.
인스턴스와의 관계에선 구체적 데이터 대신 인스턴스가 사용할 메서드만을 지니는 추상적 ‘틀’로만 작용하게 작성해야 한다. 그렇지 않으면 예기치 않은 오류가 발생할 수 있다.
하위 클래스로 삼을 생성자 함수의 prototype에 상위 클래스의 인스턴스를 부여하는 것 만으로도 기본적인 메서드 상속은 가능하나 다양한 문제가 발생할 여지가 있어 구조적으로 안전성이 떨어진다.
1. 가장 쉬운 방법
인스턴스 생성 후 프로퍼티들을 일일이 지우고 더는 새 프로퍼티를 추가할 수 없게 하는 것이다.
delete Squre.prototype.width;
delete Squre.prototype.height;
Object.freeze(Squre.prototype);
프로퍼티가 많다면 범용적으로 이런 동작을 수행하는 함수를 만들면 좋다.
2. 더글라스 크랙포드가 제시한 방법
아무 프로퍼티를 생성하지 않는 빈 생성자 함수(Bridge)를 만들어 그 Prototype이 subclass의 prototype을 바라보게 한 다음, subclass의 prototype에는 Bridge의 인스턴스를 할당하게 하는 것이다.
빈함수는 다리 역할을 한다. 이 방법으로 인스턴스를 제외한 프로토타입 체인 경로상 더는 구체적인 데이터가 남지 않는다.
var Bridge = function(){};
Bridge.prototype = Rectangle.prototype;
Square.prototype = new Bridge();
Object.freeze(Square.prototype);
3. Object.create 이용한 방법
//생략
Square.prototype = Object.create(Rectangle.prototype);
Object.freeze(Square.prototype);
//생략
이 방법은 subclass의 prototype의 proto가 superclass의 prototype을 바라보되, seperclass의 인스턴스가 되지 않아 앞의 방법들 보다 간단하고 안전하다.
위 세가지의 방법의 결론은 subclass.prototype의 proto가 superclass.prototype을 참조하고, subclass.prototype에는 불필요한 인스턴스 프로퍼티가 남아있지 않으면 된다.
var ES5 = function (name) {
this.name = name;
};
ES5.staticMethod = function() {
return this.name + ' staticMethod';
};
ES5.prototype.method = function() {
return this.name + ' method';
};
var es5Instance = new ES5('es5');
console.log(ES5.staticMethod()); // ES5 staticMethod
console.log(es5Instance.method()); // es5 method
var ES6 = class {
constructor (name) {
this.name = name;
}
static staticMethod () {
return this.name + ' staticMethod';
}
method () {
return this.name + ' method';
}
};
var es6Instance = new ES6('es6');
console.log(ES6.staticMethod()); // ES6 staticMethod
console.log(es6Instance.method()); // es6 method
{}
내부가 클래스 본문 영역이다.constructor
는 ES5의 생성자 함수와 동일한 역할을 한다.static
은 해당 메서드가 스태틱 메서드라고 알리는 내용이다.var Rectangle = class {
constructor (width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
};
var Square = class extends Rectangle {
constructor (width) {
super(width, width);
}
getArea() {
console.log('size: ', super.getArea());
}
};
extends “상속할 클래스명”
을 추가하면 Square에 상속된다.