클래스

ES6에서 도입된 클래스는 자바나 C# 같은 클래스 기반 객체지향 프로그래밍 언어와 매우 흡사한 객체 생성 메커니즘을 제시한다.

그렇다고 ES6의 클래스가 기존 프로토타입 객체지향 모델을 폐지하고 새로운 모델을 제시하는 것은 아니며 기존 프로토타입 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 하는 문법적 설탕이라고 볼 수도 있다.

클래스 정의

클래스는 class 키워드를 사용하여 정의한다.
클래스명은 파스칼 케이스를 사용하는 것이 일반적이지만,
파스칼 케이스를 사용하지 않아도 에러가 발생하지는 않는다.

// 클래스 선언문
class Person {}

일반적이지는 않지만 표현식으로 클래스를 정의할 수도 있다.
함수와 마찬가지로 이름을 가질 수도 갖지 않을 수도 있다.

// 익명 클래스 표현식
const Person = class {};

// 기명 클래스 표현식
const Person = class MyClass {};

클래스 호이스팅

클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생한다.

const Person = '';

{
  // 호이스팅이 발생하지 않는다면 ''이 출력되어야 한다.
  console.log(Person);
  // ReferenceError: Cannot access 'Person' before initialization
  
  // 클래스 선언문
  class Person {}
}

단, 클래스는 let, const 키워드로 선언한 변수처럼 호이스팅된다. 그래서 클래스 선언문 이전에 TDZ에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작한다.

인스턴스 생성

클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성한다.

class Person {}

// 인스턴스 생성
const me = new Person();
console.log(me) // Person {}

클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이므로 반드시 new 연산자와 함께 호출해야 한다.

class Person {}

// 클래스를 new 연산자 없이 출하면 타입 에러가 발생한다.
const me = Person();
// TypeError: Class constructor Foo cannot be invoked without 'new'

메서드

클래스 몸체에는 0개 이상의 메서드만 선언할 수 있다.
클래스 몸체에서 정의할 수 있는 메서드는 constructor(생성자), 프로토타입 메서드, 정적 메서드 세가지가 있다.

constructor

constructor는 인스턴스를 생성하고 초기화하기 위한 특수한 메서드다. costructor는 이름을 변경할 수 없다.

 class Person {
   // 생성자
   constructor(name) {
     // 인스턴스 생성 및 초기화
     this.name = name;
   }
 }

프로토타입 메서드

클래스 몸체에서 정의한 메서드는 프로토타입 메서드가 된다.

  class Person {
    // 생성자
    constructor(name) {
      // 인스턴스 생성 및 초기화
	  this.name = name;
    }
    
    // 프로토타입 메서드
    sayHello(){
      console.log(`Hi! my name is ${this.name}`);
    }
  }

  const me = new Person('Han');
  me.sayHello(); // Hi! my name is Han

정적 메서드

정적static 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드를 말한다.
클래스에서는 메서드에 static 키워드를 붙이면 정적 클래스 메서드가 된다.

  class Person {
    // 생성자
    constructor(name) {
    // 인스턴스 생성 및 초기화
      this.name = name;
    }

  // 정적 메서드
    static sayHi(){
      console.log(`Hi!`);
    }
  }

// 정적 메서드는 클래스로 호출한다.
// 정적 메서드는 인스턴스 없이도 호출할 수 있다.
Person.sayHi(); // Hi!

정적 메서드는 인스턴스로 호출할 수 없다.

  const me = new Person('han');
  me.sayHi(); // TypeError: me.sayHi is not a function

상속

DogBird는 각각 자신만의 고유한 속성도 있지만 공통된 속성도 가지고 있다.

class Dog {
  constructor(type) {
    this.type = type
  }
  eat() {return 'eat'}
  run() {return 'move'}
}

class Bird {
  constructor(type) {
    this.type = type
  }
  eat() {return 'eat'}
  fly() {return 'fly'}
}

동물의 공통된 속성을 뽑아 Animal클래스로 만들고 DogBird는 각각의 고유한 속성을 갖되 상속을 통해 Animal의 속성을 그대로 사용하면 재사용 측면에서 매우 유용할 것이다.

class Animal {
  constructor(type) {
    this.type = type
  }
  eat() {return 'eat'}
}

class Dog extends Animal {
  run() {return 'move'}
}

class Bird extends Animal {
  fly() {return 'fly'}
}

const dog = new Dog('hursky');
console.log(dog.type); // hursky
console.log(dog.eat()); // eat
console.log(dog.run()); // fly

extends

위에서 살펴보았듯 상속을 통해 클래스를 확장하려면 extends 키워드를 사용하여 상속받을 클래스를 정의한다.

// 수퍼(베이스/부모)클래스
class Base {}

// 서브(파생/자식)클래스
class Derived extends Base {}

동적 상속

extends키워드는 클래스뿐만 아니라 생성자 함수를 상속받아 클래스를 확장할 수도 있다.

// 생성자 함수
function Base(a) {
  this.a = a;
}

// 생성자 함수를 상속받는 서브클래스
class Derived extends Base{}

const derived = new Derived();
console.log(derived); // Derived {a: 1}

exteneds키워드 다음에는 클래스뿐만 아니라 [[Constructor]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다. 이를 통해 동적으로 상속 대상을 결정할 수 있다.

function Base1() {}

let condition = true;

// 조건에 따라 동적으로 상속 대상을 결정하는 서브클래스
class SubClass extends (condition ? Base1 : Base2) {}

const subClass = new SubClass();
console.log(subClass); // SubClass {}

super

super키워드는 함수처럼 호출할 수도 있고, this와 같이 식별자 처럼 참조할 수 있는 특수한 키워드다.

super 호출

super 호출하면 수퍼클래스의 constructor를 호출한다.

class Base {
  constructor(a,b) {
    this.a = a;
    this.b = b;
  }
}

class Derived extends Base {
  constructor(a,b,c) {
    super(a,b);
    this.c = c;
  }
}

const derived = new Derived(1,2,3);
console.log(derived); // Derived {a:1, b: 2, c: 3}

super를 호출할 때 주의 사항

  • 서브클래스에서 constructor를 생략하지 않는 경우 서브클래스의 constructor에서는 반드시 super를 호출해야 한다.

    class Base {}
    
    class Derived extends Base {
      constructor() {
        // ReferenceError: Must call super constructor in derived class 
        // before accessing 'this' or returning from derived constructor
        console.log('constructor call');
      }
    }
    		```
    
  • 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없다.

  class Base {}

  class Derived extends Base {
    constructor() {
      // ReferenceError: Must call super constructor in derived class 
      // before accessing 'this' or returning from derived constructor
      this.a = 1;
      super();
    }
  }
  • super는 반드시 서브클래스의 constructor에서만 호출한다. 서브클래스가 아닌 constructor나 함수에서 호출하면 에러가 발생한다.
  class Base {
    constructor() {
      super(); // SynctaxError: 'super' keyword unexpected here
    }
  }

  function Foo{} {
    super(); // SynctaxError: 'super' keyword unexpected here
  }

super 참조

super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.

  • 서브클래스의 프로토타입 메서드 내에서 super.sayHi는 수퍼클래스의 프로토타입 메서드 sayHi를 가리킨다.
  // 슈퍼클래스
  class Base {
    constructor(name) {
      this.name = name;
    }
    
    sayHi() {
      return `Hi! ${this.name}`;
    }
  }

  // 서브클래스
  class Derived extends Base {
    sayHi() {
      // super.sayHi는 수퍼클래스의 프로토타입 메서드를 카리킨다.
      return `${super.sayHi()}.how are you doing?`;
    }
  }

  const derived = new Derived('Han');
  console.log(derived.sayHi()); // Hi! Han. how are you doing?

0개의 댓글