클래스(class)는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀이다. 객체를 정의 하기 위한 상태(멤버변수)와 메서드(함수)로 구성된다.
class의 사전적 정의다. class는 객체지향프로그래밍(이하 OOP로 줄인다.)을 설명할때 빼놓을수 없는 컨텐츠 중 하나다. OOP에 있어 개발 프로세스는 다음과 같다.
// iPhone7의 설계도를 만든다.
class iPhone7 {
...
}
iPhone7 newPhone = new iPhone7();
newPhone.setNumber("010-0000-0000");
newPhone.setICloudId("jobs");
...
newPhone.takePicture();
...
다음과 같이 객체를 생성하고 그 객체마다 데이터 설정, 메소드 호출 등을 통해 비즈니스 로직을 수행하며 프로그램을 완성해나가는 것이 OOP 디자인 패턴이다.javascript에서도 OOP 패턴으로 코딩이 가능한데 그것을 가능케 해주는 것이 ES6에서 추가된 class 문법이다.
보통 class를 구성하는 컨텐츠는 3가지 정도로 분류할 수 잇다.
class에 대해서는 java 시리즈에서 다루도록 하겠다. 대충 이런식이라는 것만 알고 넘어가자.
기존에 ES6 이전에도 class 를 사용할 수 있었다. class 라는 예약어가 있는게 아니라 function을 class 형태로 선언해서 사용하는 문법이라고 생각하면 된다. class의 메소드(method)는 javascript에서는 함수(function)을 뜻한다. 예제 코드에선 편의상 메소드라 칭하겠다.
ES5에서는 class 전용 문법이 없기 때문에 prototype형태로 메소드를 선언해줘야한다. 이때 메소드는 2가지 형태를 띄는데 다음과 같다.
지금은 이렇게 2가지 종류의 메소드가 있다고 인지하고 넘어가자.다음은 ES5에서 class 선언 코드로 내부에 변수 name
과 static 메소드와 prototype 메소드를 포함하고 있다.
// ES5
var class1 = function (name) {
this.name = name;
};
class1.staticFunction = function () { // static 메소드
console.log(this.name + ' : call static method!');
};
class1.prototype.basicfunction = function () { // 객체가 사용할 prototype 메소드
console.log(this.name + ' : call prototype method!');
};
class1.staticFunction();
// class1 : call static method!
var instance1 = new class1('carrots');
instance1.basicfunction();
// carrots : call prototype method!
ES5에선 이런 방식으로 class 함수를 구현하여 사용할 수 있다.class1
이라는 class 함수를 만들고 new
키워드로 객체를 생성해 instance1
에 할당했다.
위 코드를 보면 static 메소드는 객체 생성없이 class1
를 통해 호출했고,prototype 메소드는 instance1
를 통해 호출한 것을 확인할 수 있다.이렇게 메소드 선언 형태는 2가지로 분류될 수 있는 것이다. 그 class가 가진 고유의 메소드인지, 생성된 객체가 가질 메소드인지의 차이다.
static 메소드와 prototype 메소드는 this
가 참조하는 대상도 다른데다음 결과를 보면 둘다 같은 this.name
을 사용했는데 static 메소드는 class1
을 참조했고, prototype 메소드는 생성시 파라미터로 전달했던 carrots
을 참조하여 출력한 것을 확인할 수 있다.
class1
자체를 참조해서 출력했다. class1.staticFunction
내부에서의 this
는 class1
함수 껍데기를 지칭하는 것이다. 함수 객체는 내부적으로 name
이라는 속성이 존재한다. 그래서 해당 class name이 참조된 것이다.instance1
은 생성된 오브젝트기 떄문에 내부에서 사용된 this
는 instance1
를 지칭한다. 그렇기 때문에 instance1
내부에 갖고있는 name
변수를 참조해 출력한 것이다.console.log
로 두녀석을 찍어보면 다음과 같다.
// ES5
console.log(instance1);
// class1 {name: 'carrots'}
console.log(class1);
/* ƒ (name) {
this.name = name;
this.aa = '';
} */
뭐 무튼 static 메소드와 prototype 메소드의 차이는 객체를 생성 여부에 있다.
ES5에서는 저렇게 특정 기능을 제공해주지않아 선언하는데 불편함이 있었다. 근데 ES6 스펙에서 본격적으로 class 라는 키워드를 만들어줬다. 훨씬 가시적이며 명확해진 코드를 보자
// ES6
const class2 = class {
constructor (name) {
this.name = name;
}
static staticFunction () {
console.log(`${this.name} : call static method!`);
}
basicfunction () {
console.log(`${this.name} : call prototype method!`);
}
};
class2.staticFunction();
// class1 : call static method!
var instance2 = new class2('carrots');
instance2.basicfunction();
// carrots : call prototype method!
와우 정말 간결해지고 가시적으로 class2
를 통해 사용할 수 있는 메소드라는 걸 알수있다. 기존에 javascript prototype으로 선언해줘야 했던 prototype 메소드는 메소드만 선언해서 사용할 수 있게 되었다. 여기서 알아두면 좋은 것은 ES6의 class 문법도 내부적으로는 javascript prototype을 사용하여 메소드를 할당한다는 것이다. 본질적으로는 같은 기능이다~ 이말이다. 에
뭐 대충 두 녀석의 내용을 정리하자면 이렇다.
편해졌다 이거다.
class 구조체는 상속이라는 개념이 있다. 부모 (상위 class)와 자식 (하위 class) 관계가 존재하며, 자식 class는 상속받은 부모 class의 변수나 메소드를 참조하여 사용할 수 있다. 상속을 사용하는 이유는 이미 구현한 class의 메소드를 재사용해서 만들 수 있기 때문에 효율적이다. 예를 들면 이런거다.
내가 혐오하는 샘숭폰이 있다. 우리는 매년 다른 버전의 혐폰 발표를 본다. 👻 (샘숭폰 유저라면 미안하다.)Galaxy1, Galaxy2, Galaxy3... 뭐 이런식으로 증식하는데, 이때 우리가 상속이라는 개념을 이해하기 위해 샘숭폰이 가진 원초적인 기능들에 대해 생각해볼 필요가 있다.샘숭 폰이 버전이 계속올라가도 포함하고 있는 기능이 뭐가 있을까?
등등등 ...뭐 이런식으로 되있지 않겠는가? 이걸 매 버전마다 계속 다시 구현하진 않을 것이다. 미리 부모 class에 공통기능을 넣어놓고 Galaxy1, Galaxy2 이런 자식 class를 만들때마다 저 부모 class의 전화 걸기, 문자 보내기등의 메소드를 호출해서 사용한다. 같은 Galaxy 핸드폰이지만 버저닝이라던지 종류가 달라 class의 기능 확장이 필요할때 사용되는 것이 바로 상속스다.한마디로 Galaxy1, Galaxy2, Galaxy3... 전부다 Galaxy지 않은가? 결론은 그말이다.
javascript class 문법에서도 다음처럼 상속의 형태를 사용할 수 있다. 참조 예시 ES5에선 진짜 이거만한 똥꼬쇼가 없는거 같은데 작성자분도 대단하다. 나도 이해를 위해 쉐도우 코딩을 했다. 헉헉..
// ES5
// 이 코드가 뭔가 하니 ES5에서는 상속을 지원하지않으니 그냥 javascript로 상속 기능을 구현한 것이다.
/**
* superClass : 부모 class
* subClass : 자식 class (ES5니까 function ())
* subMethods : 자식 class에서 확장할 메소드 목록
*/
var extendClass = function (superClass, subClass, subMethods) {
// 부모 class의 prototype을 상속
subClass.prototype = Object.create(superClass.prototype);
// 자식 class에 생성자 함수 생성
subClass.prototype.constructor = subClass;
// 상속된 자식 class에서 사용하는
// super 키워드는 부모 클래스를 지칭하는 예약어다. 이걸 그냥 만들어버리네..
subClass.prototype.super = function () {
// return 하는 함수 내부에서 this의 주체가 바뀌기 때문에 self 변수에 할당
var self = this;
return function () {
superClass.apply(self, arguments);
}
};
// 확장할 메소드들이 있다면 그 확장기능을 prototype 메소드로 선언
if (subMethods) {
for (var method in subMethods) {
subClass.prototype[method] = subMethods[method];
}
}
// subClass의 prototype을 변경할 수 없게 freeze
Object.freeze(subClass.prototype);
return subClass;
};
// 부모 class를 생성
var Galaxy = function (number, message) {
this.number = number;
this.message = message;
};
Galaxy.prototype.phoneCall = function () {
console.log('call to ' + this.number);
};
// 부모 class 상속받은 Galaxy1을 생성
var Galaxy1 = extendClass(
Galaxy, // 부모 class
function (number, message) { // 자식 class
this.super()(number, message);
}, { // 확장할 메소드 object 타입으로 전달
sendMessage : function () {
console.log(this.number + ' : ' + this.message);
}
}
);
var newGalaxy1 = new Galaxy1('010-0000-1111', 'tq');
newGalaxy1.sendMessage();
newGalaxy1.phoneCall();
// 010-0000-1111 : tq
// call to 010-0000-1111
작성자에게 속았다. 그대로 하면 안된다. 칙쇼... 와타시노 시켄이...
그래서 잘되게끔 처리했당. ㅎㅎ 뭐 대충 내용을 보면 ES5에선 상속 키워드가 따로 없으니 만들어서 쓰는 내용이다. 아무튼 ES5에서 쓰려면 이런 불필요한 작업이 늘어난다.
다음은 ES6를 보자
// ES6
const Galaxy = class {
constructor (number, message) {
this.number = number;
this.message = message;
}
phoneCall () {
console.log(`call to ${this.number}`);
}
};
const Galaxy1 = class extends Galaxy {
constructor (number, message) {
super(number, message);
}
sendMessage () {
console.log(`${this.number} : ${this.message}`);
}
};
const newGalaxy1 = new Galaxy1('010-0000-1111', 'tq');
newGalaxy1.sendMessage();
newGalaxy1.phoneCall();
// 010-0000-1111 : tq
// call to 010-0000-1111
엄청 간결해졌다. 이와 같이 ES6에 들어서면서 class 사용법도 굉장히 간편해졌다.
`${this.name} 돈을 달란말이야 그러면 더 신기술을 쓸수있다!