본문 바로가기
Javascript

[Javascript] ES6 - 클래스

by 기리의 개발로그 2023. 11. 28.

클래스

자바스크립트는 프로토타입 기반 객체지향 언어로 클래스가 필요없는(class-free) 객체지향 프로그래밍 스타일로 프로토타입 체인과 클로저 등으로 객체 지향 언어의 상속, 캡슐화(정보 은닉) 등의 개념을 구현할 수 있었다.

하지만 ES6에서는 클래스에 대한 새로운 문법을 다음과 같이 제시한다.


클래스 정의


ES6 클래스는 class 키워드를 사용한다.

// 클래스 선언문
class Person {
  // constructor(생성자)
  constructor(name) {
    this._name = name;
  }

  say() {
    console.log(`Hi! ${this._name}`);
  }
}

// 인스턴스 생성
const me = new Person('Lee');
me.say();  // Hi! Lee

console.log(me instanceof Person);  // true

인스턴스 생성


new 키워드를 사용한다.

class Foo {}

const foo = new Foo();

생성자(constructor)


생성자 는 인스턴스를 생성하고 클래스 필드(클래스 내부의 변수)를 초기화하기 위한 메소드이다.

// 클래스 선언문
class Person {
  // constructor(생성자). 이름을 바꿀 수 없다.
  constructor(name) {
    // this는 클래스가 생성할 인스턴스를 가리킨다.
    // _name은 클래스 필드이다.
    this._name = name;
  }
}

// 인스턴스 생성
const me = new Person('Lee');
console.log(me);  // Person {_name: "Lee"}

생성자는 클래스 내에서 한개만 존재할 수 있으며 생략도 가능하다. 생략하면 constructor() {} 를 호출한 것과 동일하며 빈 객체를 생성한다. 만약 프로퍼티를 추가하려면 인스턴스를 생성한 이후 동적으로 추가해야 한다.

class Foo { }

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

// 프로퍼티 동적 할당 및 초기화
foo.num = 1;
console.log(foo);  // Foo { num: 1 }

class Foo {
  // constructor는 인스턴스의 생성과 동시에 클래스 필드의 생성과 초기화를 실행한다.
  constructor(num) {
    this.num = num;
  }
}

const foo = new Foo(1);

console.log(foo);  // Foo { num: 1 }

클래스 필드


클래스 필드(멤버 변수)의 선언과 초기화는 생성자 내부에서 해야한다.

class Foo {
  constructor(name = '') {
    this.name = name;  // 클래스 필드의 선언과 초기화
  }
}
const foo = new Foo('Lee');

console.log(foo);  // Foo { name: 'Lee' }
console.log(foo.name);  // Lee

getter, setter


getter

getter 는 클래스 필드에 접근할 때마다 클래스 필드의 값을 조작하는 행위가 필요할 때 사용하며 메소드 이름 앞에 get 키워드를 사용해 정의한다.

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

  // getter: get 키워드 뒤에 오는 메소드 이름 firstElem은 클래스 필드 이름처럼 사용된다.
  get firstElem() {

    return this._arr.length ? this._arr[0] : null;
  }
}

const foo = new Foo([1, 2]);
// 필드 firstElem에 접근하면 getter가 호출된다.
console.log(foo.firstElem);  // 1

setter

setter 는 클래스 필드에 값을 할당할 때마다 클래스 필드의 값을 조작하는 행위가 필요할 때 사용하며 메소드 이름 앞에 set 키워드를 사용해 정의한다.

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

  // getter: get 키워드 뒤에 오는 메소드 이름 firstElem은 클래스 필드 이름처럼 사용된다.
  get firstElem() {
    // getter는 반드시 무언가를 반환하여야 한다.
    return this._arr.length ? this._arr[0] : null;
  }

  // setter: set 키워드 뒤에 오는 메소드 이름 firstElem은 클래스 필드 이름처럼 사용된다.
  set firstElem(elem) {

    this._arr = [elem, ...this._arr];
  }
}

const foo = new Foo([1, 2]);

// 클래스 필드 lastElem에 값을 할당하면 setter가 호출된다.
foo.firstElem = 100;

console.log(foo.firstElem);  // 100

정적 메소드


static 키워드를 사용하여 정적 메소드를 정의한다. 정적 메소드는 클래스의 인스턴스가 아닌 클래스 이름으로 호출하며 this를 사용할 수 없다.

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

  static staticMethod() {
    /*
    정적 메소드는 this를 사용할 수 없다.
    정적 메소드 내부에서 this는 클래스의 인스턴스가 아닌 클래스 자신을 가리킨다.
    */
    return 'staticMethod';
  }

  prototypeMethod() {
    return this.prop;
  }
}

// 정적 메소드는 클래스 이름으로 호출한다.
console.log(Foo.staticMethod());

const foo = new Foo(123);

// 정적 메소드는 인스턴스로 호출할 수 없다.
console.log(foo.staticMethod()); // Uncaught TypeError

클래스 상속


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

  • 오버라이딩(Overriding)
    상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의하여 사용하는 방식이다.

  • 오버로딩(Overloading)
    매개변수의 타입 또는 갯수가 다른, 같은 이름의 메소드를 구현하고 매개변수에 의해 메소드를 구별하여 호출하는 방식이다.

// 부모 클래스
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 메소드를 오버라이딩하였다.
  getArea() {
    return (this.height * super.getPerimeter()) + (2 * super.getArea());
  }

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

// 반지름이 2, 높이가 10인 원통
const cylinder = new Cylinder(2, 10);

// 원의 지름
console.log(cylinder.getDiameter());   // 4
// 원의 둘레
console.log(cylinder.getPerimeter());  // 12.566370614359172
// 원통의 넓이
console.log(cylinder.getArea());       // 150.79644737231007
// 원통의 부피
console.log(cylinder.getVolume());     // 125.66370614359172

// cylinder는 Cylinder 클래스의 인스턴스이다.
console.log(cylinder instanceof Cylinder);  // true
// cylinder는 Circle 클래스의 인스턴스이다.
console.log(cylinder instanceof Circle);    // true

super

super 키워드는 부모 클래스를 참조(Reference)할 때 또는 부모 클래스의 생성자를 호출할 때 사용한다.

// 부모 클래스
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 메소드는 부모 클래스의 constructor를 호출하면서 인수를 전달한다.
    super(radius);
    this.height = height;
  }

  getArea() {

    // super 키워드는 부모 클래스(Base Class)에 대한 참조
    return (this.height * super.getPerimeter()) + (2 * super.getArea());
  }

  getVolume() {

    // super 키워드는 부모 클래스(Base Class)에 대한 참조
    return super.getArea() * this.height;
  }
}

// 반지름이 2, 높이가 10인 원통
const cylinder = new Cylinder(2, 10);
반응형

'Javascript' 카테고리의 다른 글

[Javascript] ES6 - Symbol  (77) 2023.11.30
[Javascript] ES6 - 모듈  (80) 2023.11.29
[Javascript] ES6 - 디스트럭처링  (86) 2023.11.27
[Javascript] ES6 - Spread 연산자  (84) 2023.11.24
[Javascript] ES6 - Rest 파라미터  (57) 2023.11.23

댓글