ES2015 이전까지는 비슷한 종류 객체를 많이 만들어내기 위해 **생성자**를 사용해왔습니다.
ES2015에서 도입된 **클래스**는 생성자의 기능을 대체합니다. `class` 표현식을 사용하면, 생성자와 같은 기능을 하는 함수를 훨씬 더 깔끔한 문법으로 정의할 수 있습니다.
```js
// 클래스 기본 코드
classPerson{
// 이전에서 사용하던 생성자 함수는 클래스 안에 `cosntructor`라는 이름으로 정의합니다.
constructor({name,age}){
this.name=name;
this.age=age;
}
// 객체에서 메소드를 정의할 때 사용하던 문법을 그대로 사용하면, 메소드가 자동으로 `Person.prototype`에 저장됩니다.
introduce(){
return`안녕하세요, 제 이름은 ${this.name}입니다.`;
}
}
constperson=newPerson({name:"윤아준",age:19});
console.log(person.introduce());// 안녕하세요, 제 이름은 윤아준입니다.
```
`class` 블록에서는 JavaScript의 다른 곳에서는 사용되지 않는 별도의 문법으로 코드를 작성해야합니다. 함수 혹은 객체의 내부에서 사용하는 문법과 혼동하지 않도록 주의하세요.
```js
// 클래스는 함수가 아닙니다!
classPerson{
console.log('hello');
}
// 에러: Unexpected token
```
```js
// 클래스는 객체가 아닙니다!
classPerson{
prop1:1,
prop2:2
}
// 에러: Unexpected token
```
문법이 아니라 동작방식의 측면에서 보면, ES2015 이전의 생성자와 ES2015의 클래스는 다음과 같은 차이점이 있습니다.
- 클래스는 **함수로 호출될 수 없습니다.**
- 클래스 선언은 `let`과 `const`처럼 **블록 스코프**에 선언되면, **호이스팅(hosting)**이 일어나지 않습니다.
- 클래스의 메소드 안에서 `super`키워드를 사용할 수 있습니다
---
🔖 블록 스코프
중괄호({})내부에서 `const` 또는 `let`으로 변수를 선언하면, 그 변수들은 중괄호 블록 내부에서만 접근할수 있습니다.
🔖 호이스팅(hosting)
var로 선언된 변수나 function의 선언부를 현재 scope의 최상단으로 끌어올려지는 기능입니다.
- var는 함수 스코프이기 때문에 함수 내부에서 맨 상단으로 끌어올려짐.
---
## 2. ES6 Class 문법
JavaScript **Class** 는 ECMAScript 6을 통해 소개되었습니다. ES6의 Class는 기존 prototype 기반의 상속을 보다 명료하게 사용할 수 있도록 문법을 제공합니다. 이를 Syntatic Sugar라고 부르기도 합니다.
> Syntatic Sugar : 읽고 표현하는것을 더 쉽게 하기 위해서 고안된 프로그래밍 언어 문법을 말합니다.
JavaScript를 ES6를 통해 처음 접하시는 분들은 알아두셔야할 것이 JavaScript의 Class는 다른 객체지향 언어(C++, C#, Java, Python, Ruby 등...)에서 사용되는 Class 문법과는 다르다는 것입니다. JavaScript에는 Class라는 개념이 없습니다. Class가 없기 때문에 기본적으로 Class 기반의 상속도 불가능합니다. 대신 다른 언어에는 존재하지 않는 **프로토타입(Prototype)**이라는 것이 존재합니다. JavaScript는 이 **prototype을 기반으로 상속을 흉내내도록 구현해 사용합니다.**
## 3. Class 정의
JavaScript에서 Class는 사실 함수입니다. 함수를 함수 선언과 함수 표현식으로 정의할 수 있듯이 class 문법도 class 선언과 class 표현식 두가지 방법으로 정의가 가능합니다.
JavaScript 엔진은 function 키워드를 만나면 Function 오브젝트를 생성하듯, class 키워드를 만나면 Class 오브젝트를 생성합니다. class는 클래스를 선언하는 키워드이고 Class 오브젝트는 엔진이 class 키워드로 생성한 오브젝트입니다.
### Class 선언
함수 선언과 달리 클래스 선언은 호이스팅이 일어나지 않기 때문에, 클래스를 사용하기 위해서는 먼저 선언을 해야합니다. 그렇지 않으면 ReferenceError 가 발생합니다.
new 연산자를 사용하지 않고 constructor를 호출하면 타입 에러(TypeError)가 발생한다. constructor는 new 연산자 없이 호출할 수 없다.
```js
classFoo{}
constfoo=Foo();// TypeError: Class constructor Foo cannot be invoked without 'new'
```
## 5. constructor
constructor는 인스턴스를 생성하고 클래스 프로퍼티를 초기화하기 위한 특수한 메소드이다. constructor는 클래스 내에 한 개만 존재할 수 있으며 만약 클래스가 2개 이상의 constructor를 포함하면 문법 에러(SyntaxError)가 발생한다. 인스턴스를 생성할 때 new 연산자와 함께 호출한 것이 바로 constructor이며 constructor의 파라미터에 전달한 값은 클래스 프로퍼티에 할당한다.
constructor는 생략할 수 있다. constructor를 생략하면 클래스에 `constructor() {}`를 포함한 것과 동일하게 동작한다. 즉, 빈 객체를 생성한다. 따라서 클래스 프로퍼티를 선언하려면 인스턴스를 생성한 이후, 클래스 프로퍼티를 동적 할당해야 한다.
```js
classFoo{}
constfoo=newFoo();
console.log(foo);// Foo {}
// 클래스 프로퍼티의 동적 할당 및 초기화
foo.num=1;
console.log(foo);// Foo { num: 1 }
```
constructor는 인스턴스의 생성과 동시에 클래스 프로퍼티의 생성과 초기화를 실행한다.
```js
classFoo{
// constructor는 인스턴스의 생성과 동시에 클래스 프로퍼티의 생성과 초기화를 실행한다.
constructor(num){
this.num=num;
}
}
constfoo=newFoo(1);
console.log(foo);// Foo { num: 1 }
```
## 7. 클래스 프로퍼티
클래스 몸체(class body)에는 메소드만 선언할 수 있다. 클래스 바디에 클래스 프로퍼티(멤버 변수)를 선언하면 문법 에러(SyntaxError)가 발생한다.
```js
classFoo{
name="";// SyntaxError
constructor(){}
}
```
클래스 프로퍼티의 선언과 초기화는 반드시 constructor 내부에서 실시한다.
```js
classFoo{
constructor(name=""){
this.name=name;// 클래스 프로퍼티의 선언과 초기화, public 클래스 프로퍼티
}
}
constfoo=newFoo("Lee");
console.log(foo);// Foo { name: 'Lee' }, 클래스 외부에서 참조할수 있다.
```
constructor 내부에서 선언한 클래스 프로퍼티는 클래스의 인스턴스를 가리키는 this에 바인딩한다. 이로써 클래스 프로퍼티는 클래스가 생성할 인스턴스의 프로퍼티가 되며, 클래스의 인스턴스를 통해 클래스 외부에서 언제나 참조할 수 있다. 즉, 언제나 public이다.
## 6. 메소드 정의하기
클래스의 메소드를 정의할 때는 객체 리터럴에서 사용하던 문법과 유사한 문법을 사용합니다.
인스턴스 메소드는 다음과 같은 문법을 통해 정의합니다.
```js
classCalculator{
add(x,y){
returnx+y;
}
subtract(x,y){
returnx-y;
}
}
```
---
🔖 객체 리터럴
객체는 한꺼번에 여러 값을 담을 수 있는 통과 같은 자료구조입니다. 객체 안에서 이름-값 쌍이 저장되는데, 이를 객체의 속성이라고 합니다.
```js
constperson={
name:"윤아준",// 속성 이름 - 'name', 속성 값 - '윤아준'
age:19,// 속성 이름 - 'age', 속성 값 - 19
languages:["Korean","English"],// 속성 이름 - 'languages', 속성 값 - 배열
"한국 나이":20// 속성 이름 - '한국 나이', 속성 값 - 20
};
```
🔖 인스턴스 (Instance)
생성자를 통해 생성된 객체를 그 생성자의 인스턴스(instance)라고 합니다.
```js
// 생성자 정의
functionPerson(name){
this.name=name;
}
// 생성자를 통한 객체 생성
// person1이 Person의 인스턴스입니다.
constperson1=newPerson("윤아준");
```
🔖 메소드(method)
어떤 객체의 속성으로 접근해서 사용하는 함수를 메소드(method)
```js
constperson={
greet:function(){
return"hello";
}
};
person.greet();// 'hello';
```
---
객체 리터럴의 문법과 마찬가지로, 임의 표현식을 **대괄호**로 둘러싸서 메소드의 이름으로 사용할수도 있습니다.
```js
constmethodName='introduce';
classPerson{
constructor({name,age}){
this.name=name;
this.age=age;
}
// 아래 메소드의 이름은 `introduce`가 됩니다.
[methodName]{
return`안녕하세요, 제 이름은 ${this.name}입니다.`;
}
}
console.log(newPerson({name:'윤아준',age:19}).introduce());// 안녕하세요, 제 이름은 윤아준입니다.
```
**Getter** 혹은 **Setter**를 정의하고 싶을 때는 메소드 이름 앞에 `get` 또는 `set`을 붙여주면 됩니다.
JavaScript의 함수는 객체이기도 하다는 사실을 앞에서 언급했습니다. 생성자의 속성에 직접 지정된 메소드를 가지고 정적메소드라고 합니다.
정적 메소드는 특정 인스턴스에 대한 작업이 아니라, 해당 생성자와 관련된 일반적인 작업을 정의하고 싶을때 사용됩니다.
정적인 이유는 생성자를 직접 넣어주면 정적이라고 합니다.
그냥 메소드랑 정적 메소드는 저장장소가 다르며 this메소드를 사용 하지 않을때 사용 합니다.
```js
// 생성자의 속성에 함수를 직접 할당합니다.
Person.compareAge=function(person1,person2){
if (person1.age<person2.age){
return"첫 번째 사람의 나이가 더 많습니다.";
}elseif (person1.age===person2.age){
return"두 사람의 나이가 같습니다.";
}else{
return"두 번째 사람의 나이가 더 많습니다.";
}
};
```
---
Generator 메소드를 정의하려면, 메소드 이름 앞에 `*` 기호를 붙여주면 됩니다.
아래와 같이 `Symbol.iterator` 메소드를 generator로 정의해주면, 클래스의 인스턴스를 쉽게 iterable로 만들 수 있습니다.
```js
classGen{
*[Symbol.iterator](){
yield1;
yield2;
yield3;
}
}
// 1, 2, 3이 차례대로 출력됩니다.
for (letnofnewGen()){
console.log(n);
}
```
## 7. 클래스 필드(Class Field)
클래스 블록 안에서 할당 연산자(=)를 이용해 인스턴스 속성을 지정할 수 있는 문법을 **클래스 필드(class field)**라고 합니다.
```js
classCounter{
staticinitial=0;// static class field
count=Counter.initial;// class field
inc(){
returnthis.count++;
}
}
constcounter=newCounter();
console.log(counter.inc());// 0
console.log(counter.inc());// 1
Counter.initial=10;
console.log(newCounter().count);// 10
```
클래스 필드는 아직 정식 표준으로 채택된 기능은 아닙니다. 아직 이 기능을 구현한 브라우저는 없는 상태이고, Babel, TypeScript 등의 트랜스파일러를 통해 일부 기능을 사용할 수 있습니다.
### 클래스 필드와 this
`class` 블록은 새로운 블록 스코프를 형성하고, 이 내부에서 사용된 `this`는 인스턴스 객체를 가리키게 됩니다.
```js
classMyClass{
a=1;
b=this.a;
}
newMyClass().b;//1
```
이 성질을 이용하면, **화살표 함수를 통해서 메소드를 정의할 수 있습니다.**
```js
classMyClass{
a=1;
getA=()=>{
returnthis.a;
};
}
newMyClass().getA();// 1
```
일반적인 메소드와 동작방식 측면에서 광장히 큰 차이점이 있습니다.
1. 일반적인 메소드는 클래스의 `prototype`속성에 저장되는 반면, **클래스필드는 인스턴스 객체에 저장됩니다.**
1. 화살표 함수의 `this`는 호출 형태에 관계없이 항상 인스턴스 객체를 가리키게 됩니다.
2번의 성질로 **메소드를 값으로 다루어야 할 경우**에는 일반적인 메소드 대신 화살표 함수가 사용되는 경우가 종종 있습니다. 다만, 일반적인 메소드와 달리, 클래스 필드를 통해 정의한 메소드는 **인스턴스를 생성할 때마다 새로 생성되기 때문에** 메모리를 더 차지하게 되므로 주의해서 사용해야 합니다.
메소드를 다른 함수로 넘겨 줘야 되는 경우는 화살표 함수를 사용 하는것이 좋습니다.
## 8. 클래스 상속
클래스 상속기능을 통해 한 클래스의 기능을 다른 클래스에서 **재사용**할 수 있습니다.
```js
classParent{
// ...
}
classChildextendsParent{
// ...
}
```
`extends` 키워드를 통해 `Child` 클래스가 `Parent`클래스를 상속했습니다. 이 관계를 보고 '부모클래스 - 자식 클래스 관계' 혹은 '슈퍼클래스 - 서브클래스관계'라고 말하기도 합니다.
어떤 클래스 A가 다른 클래스 B를 상속받으면, 다음과 같은 일들이 가능해집니다.
- 자식 클래스 A를 통해 부모 클래스 B의 **정적 메소드와 정적 속성**을 사용할 수 있습니다.
- 부모 클래스 B의 **인스턴스 메소드와 인스턴스 속성**을 자식 클래스 A의 인스턴스에서 사용할 수 있습니다.
```js
classParent{
staticstaticProp="staticProp";
staticstaticMethod(){
return"I'm a static method.";
}
instanceProp="instanceProp";
instanceMethod(){
return"I'm a instance method.";
}
}
classChildextendsParent{}
// 자식요소에 빈 객체이지만 부모에게 상속 받았기 때문에 다음과 같이 출력 됩니다.
console.log(Child.staticProp);// staticProp
console.log(Child.staticMethod());// I'm a static method.
constc=newChild();
console.log(c.instanceProp);// instanceProp
console.log(c.instanceMethod());// I'm a instance method.
```
### super
자식 클래스에서 부모 클래스의 정적 속성과 인스턴스 속성에 접근할 수 있습니다. 하지만, 자식클래스에 **같은 이름의 속성**을 정의한 경우 문제가 생깁니다.
```js
classMelon{
getColor(){
return"제 색깔은 초록색입니다.";
}
}
classWaterMelonextendsMelon{
getColor(){
return"속은 빨강색입니다.";
}
}
constwaterMelon=newWaterMelon();
waterMelon.getColor();// 속은 빨강색입니다.
```
이런 경우, `super` 키워드를 통해 부모 클래스의 메소드에 직접 접근할 수 있습니다.
```js
classMelon{
getColor(){
return"제 색깔은 초록색입니다.";
}
}
classWaterMelonextendsMelon{
getColor(){
returnsuper.getColor()+" 하지만 속은 빨강색입니다.";
}
}
constwaterMelon=newWaterMelon();
waterMelon.getColor();// 제 색깔은 초록색입니다. 하지만 속은 빨강색입니다.
```
`super` 키워드의 동작 방식은 다음과 같습니다.
- 생성자 내부에서 `super`를 함수처럼 호출하면, 부모 클래스의 생성자가 호출됩니다.
- 정적 메소드 내부에서는 `super.prop`과 같이 써서 부모 클래스의 `prop`정적 속성에 접근할 수 있습니다.
- 인스턴스 메소드 내부에서는 `super.prop`과 같이 써서 부모 클래스 `prop` 인스턴스 속성에 접근할 수 있습니다.