ES6 Class

Class에 대하여

원래 자바스크립트는 프로토타입 기반 객체지향 언어이다.

프로토타입이란?

그래서 프로토타입 체인, 클로저를 이용하여 class없이 상속과 캡슐화 등을 구현할 수 있다.

프로토타입 예제

var Person = (function () {
  function Person(name) {
    this._name = name;
  }

  Person.prototype.sayHi = function() {
    console.log('Hi! ' + this._name)
  };

  return Person;
}());

var me = new Person('Jang');
me.sayHi();
console.log(me instanceof Person);

Person 생성자 함수가 new 생성자로 객체를 생성하면 [[prototype]]에 의해 Person.prototype에 있는 sayHi() 함수를 불러 올 수 있다.

그렇다면 class를 왜?

클래스 기반에 언어를 익힌 사람들에게 편리성을 제공하기 위해 ES6에서는 class라는 함수를 만들었다.

위 예제를 class로 정의한다면

class Person {
  constructor(name) {
    this._name = name;
  }
  sayHi() {
    console.log(`Hi! ${this._name}`);
  }
}

const me = new Person('Jang');
me.sayHi();
console.log(me instanceof Person);

중요한 점은 class는 표현식으로 정의하는 것을 자제해야 하는데 클래스 표현식에서 사용한 클래스 이름은 외부에서 접근할 수 없기 때문이다.

constructor

constructor는 인스턴스를 생성하고 클래스 프로퍼티를 초기화하기 위한 메서드이다. 클래스 내에 한 개만 존재하며 new 연산자를 이용해 호출한다.
또한 생략이 가능하며 생략 시 빈 객체가 생성된다. 결국 클래스 프로퍼티를 선언하려면 인스턴스를 생성하고 클래스 프로퍼티를 동적으로 할당해줘야 한다.

class Foo {}

const foo = new Foo();
console.log(foo) // ???

foo.num = 1;
console.log(foo);

클래스 프로퍼티

클래스 몸체에는 메서드만 선언할 수 있기 때문에 클래스 프로퍼티는 반드시 constructor 안에서 선언과 초기화를 진행해야 한다.
이 클래스 프로퍼티는 this에 바인딩 된다. 그러면서 클래스 프로퍼티는 생성할 인스턴스의 프로퍼티가 되고 클래스 외부에서 언제나 참조가 가능하게 된다.
주의할 점은 접근 제한자를 지원하지 않는다.

호이스팅

클래스는 let, const 처럼 호이스팅 되지 않는 것처럼 동작한다.(TDZ) 선언문 전에 참조하면 참조 에러가 발생한다.
여기서 알 수 있는 것은 클래스는 결국 함수고 함수 표현식처럼 동작한다는 것이다.

{$ link TDZ https://github.com/wonism/TIL/blob/master/front-end/javascript/tdz.md%}

getter, setter

getter는 클래스 프로퍼티에 접근할 때마다 클래스 프로퍼티의 값을 조작해야 할 때 사용한다. get 키워드를 사용하여 정의하며 메서드 이름은 클래스 프로퍼티 이름처럼 사용한다. (참조 형식) 무언가를 얻을 때 사용하기 떄문에 반드시 반환값이 있어야 한다.

class Foo {
  constructor(arr = []) {
    this._arr = arr;
  }

  get firstElem() {
    return this._arr.length ? this._arr[0] : null;
  }
}

const foo = new Foo([1,2]);
console.log(foo.firstElem); // ???

setter는 클래스 프로퍼티에 값을 할당할 때 클래스 프로퍼티 값을 조작해야 할 때 사용한다. set 키워드를 사용하여 정의하며 마찬가지로 메서드 이름은 클래스 프로퍼티 이름처럼 사용할 수 있다. setter는 호출이 아니라 값을 할당하는 것이기 때문에 메서드가 호출된다.

class Foo {
  constructor(arr = []) {
    this._arr = arr;
  }

  get firstElem() {
    return this._arr.length ? this._arr[0] : null;
  }

  set firstElem(elem) {
    this._arr = [elem, ...this._arr];
  }
}


const foo = new Foo([1,2,3]);
foo.firstElem = 100;
console.log(foo.firstElem);

static 메서드

정적 메서드란 인스턴스에 따라 달라지지 않는 메서드를 말한다. static 키워드를 사용하며 클래스 이름으로 호출한다. 인스턴스에 따라 달라지지 않기 때문에 클래스의 인스턴스를 생성하지 않아도 호출이 가능하다.

class Foo {
  constructor(prop) {
    this.prop = prop;
  }

  static staticMethod() {
    return 'staticMethod';
  }

  prototypeMethod() {
    return this.prop;
  }
}

console.log(Foo.staticMethod());
const foo = new Foo(123);
console.log(foo.staticMethod()); // TypeError

하지만 인스턴스를 호출할 수 없기 때문에 this를 사용할 수 없다. 주로 Math 객체의 메서드처럼 애플리케이션 전역에서 사용할 유틸리티 함수를 생성할 때 주로 사용한다.

프로토타입의 개념으로 살펴보자면 staticMethod는 Foo 생성자 함수의 메서드이고 prototypeMethod는 프로토타입 객체의 메서드이다. 따라서 foo 객체는 [[prototype]]에 의해서 prototype 객체 메서드를 참조할 수는 있지만(프로토타입 체인) Foo 생성자 함수의 메서드는 참조할 수 없다.

클래스 상속

extends 키워드는 부모 클래스를 상속받는 자식 클래스를 정의할 때 사용한다.

class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  getDiameter() {
    return 2 * this.radius;
  }

  getPerimeter() {
    return 2 * Math.PI * this.radius;
  }

  getArea() {
    return Math.PI * Math.pow(this.radius,2);
  }
}

class Cylinder extends Circle {
  constructor(radius, height) {
    super(radius);
    this.height = height;
  }

  getArea() {
    return (this.height * super.getPerimeter()) + (2 * super.getArea());
  }

  getVolume() {
    return super.getArea() + this.height;
  }
}

const cylinder = new Cyliner(2, 10);

console.log(cylinder.getDiameter());
console.log(cylinder.getPerimeter());

//더 찍어보세요!

오버라이딩과 오버로딩

오버라이딩은 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식이다. 오버로딩은 매개변수의 타입 또는 개수가 다르지만 같은 이름의 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식이다. JS에서는 오버로딩 자체는 지원하지 않고 arguments 객체를 사용하여 구현한다.

클래스 상속 역시 프로토타입 체이닝으로 구현되어 있기 때문에 부모 클래스의 메서드를 자식 클래스가 사용할 수 있다.

super

여기서 사용된 super 키워드는 부모 클래스를 참조하거나 constructor를 호출할 때 사용하는 키워드다. 부모 클래스의 인스턴스를 호출할 수도 있다. 또한 super를 사용하지 않으면 자식 클래스는 이게 뭔지 알 수 없기 때문에 this를 사용할 수 없다.

static 메서드와 prototype 메서드의 상속

자식 클래스의 proto는 부모 클래스이다. (프로토타입 체인) 그렇기 때문에 super 키워드를 이용하여 자식 클래스의 정적 메서드에서 부모 클래스의 정적 메서드도 호출할 수 있다. 하지만 프로토타입 메서드에서는 정적메서드를 호출할 수 없다.
자식 클래스의 인스턴스 자체로는 프로토타입 체인으로 정적 메서드를 참조할 수 없기 때문이다.