본문 바로가기

Javascript/이론+예제

[ES6 문법] 클래스 (Class)

 

Do it! 리액트 프로그래밍 정석 책과 다른 책을 참고하며 공부한 내용을 정리하는 글입니다.

 


 

클래스 (Class)에 대해서

 

- 기존 자바스크립트는 클래스 표현식이 없어서 프로토타입 기반(prototype-based) 객체지향 언어

-  클래스는 객체 지향 프로그래밍 (OOP) 을 정의하는 개념 중 하나

- 똑같은 객체를 여러 번 다시 찍어내기 위한 틀 , 즉 템플릿 (template) 이다.

동일한 변수와 메서드 등을 가지는 객체를 붕어빵에 비유한다면, 클래스는 붕어빵 틀에 비유할 수 있음

 


 

클래스 (Class) 와 인스턴스 (instance)

 

- 인스턴스 (instance) 란 객체를 만들기 위한 틀 (template) 을 의미하는 클래스를 실제로 사용할 수 있는 객체로 만드는 것

- 클래스로 구현된 구체적인 실체를 의미함

클래스가 붕어빵 틀이라면, 인스턴스는 그것을 사용해 만든 붕어빵이라고 할 수 있음

 


 

기존 자바스크립트의 클래스  표현 방법

 

함수를 생성자(constructor) 형태로 선언한 다음 상속이 필요한 변수나 함수를 prototype 객체에 할당하는 방법

function Shape (x,y, name){
    this.name = name;
    this.move(x,y);
}

Shape.prototype = {
    move: function(x,y){
        this.x = x;
        this.y = y;
    },
    area : function(){
        return 0;
    }
}

const s = new Shape(3,6, "Yu Jin")



console.log(s)
=> Shape {name: 'Yu Jin', x: 3, y: 6}

console.log(s.area())
=> 0​


위와 같이 new Shap(0, 0) 처럼 함수를 호출하면
this 객체에 해당하는 변수 및 함수(Shape)가 prototype 객체에 선언된 변수와 함수(move, area)를 함께 참조함.

예를 들어,
Shape 함수 (즉, 생성자) 에는 this.move 에 함수를 정의하지 않았지만 prototype 객체에 move() 함수가 정의되어있음
prototype 선언 이후 new 연산자로 Shape 함수를 호출하여 Shape 객체 s 를 만듬
s 는 prototype 객체 안에 있는 move 함수를 참조할 수 있음.

클래스의 상속은 prototype 객체의 값을 객체 내장 함수를 사용해 덮어씌우는 방식을 이용함

prototype 객체는 new 연산자로 생성되는 객체 안에서 this 연산자의 함수 및 변수 선언 위치를 참조할 수 있는 요소

 

prototype 객체의 값을 객체 내장 함수를 사용해 덮어씌우는 방식

function Shape (x,y, name){
    this.name = name;
    this.move(x,y);
}

Shape.prototype = {
    move: function(x,y){
        this.x = x;
        this.y = y;
    },
    area : function(){
        return 0;
    }
}

function Circle(x, y, radius){
    Shape.call(this, x, y);
    this.radius = radius;
}

Object.assign(Circle.prototype, Shape.prototype, {
    area : function(){
        return this.radius * this.radius;
    }
})

const c = new Circle(0, 0, 10)

console.log(c.area())


1. 자식 클래스 Circle 은 내장 함수 call() 을 통해 부모의 생성자를 호출하여 초깃값을 상속 받음
2. 부모 클래스 함수를 상속하는 방법으로는 Object에 내장 된 assign() 함수를 사용
3. assign() 함수에 전달한 area() 함수는 Shape.prototype 에 정의된 area() 함수를 덮어씌움

 


 

ES6 클래스 사용 방법

 

클래스 정의하기 (Class Definition)

 

1. 클래스는 클래스 선언문 이전에 참조할 수 없다

class Shape {

    name = "shape"
    
    constructor(x,y){
        this.move(x,y)
    }

    move(x,y){
        this.x = x;
        this.y = y;
    }

    area(){
        return 0
    }
}

const s = new Shape(0, 0)

console.log(s.area())
=> 0

console.log(s)
=> Shape {name: 'shape', x: 0, y: 0}

 

2.  호이스팅이 발생하지 않는가?

console.log(new Shape(0,0))
=> Cannot access 'Shape' before initialization

class Shape {

    name = "shape"

    constructor(x,y){
        this.move(x,y)
    }

    move(x,y){
        this.x = x;
        this.y = y;
    }

    area(){
        return 0
    }
}

- 클래스는 var 키워드로 선언한 변수처럼 호이스팅되지 않는다
- let, const 키워드로 선언한 변수처럼 호이스팅된다
- 클래스 선언문 이전에 일시적 사각지대(TDZ) 에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작함

 

3. 호이스팅이 발생하는가?

const Foo = '';

console.log(Foo)
=> ''

{
    console.log(Foo)
    => error

    class Foo {}
}

- 클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생
- 호이스팅은 var , let, const, function, class 키워드를 사용한 모든 선언문에 적용됨
- 모든 선언문은 런타임 이전에 먼저 실행된다

 

인스턴스 생성

 

1. 생성자 함수와 같이 new 연산자와 함께 클래스 이름을 호출하면 클래스의 인스턴스가 생성.

class Foo {}

const foo = new Foo();​

 

2. 표현식이 아닌 선언식으로 정의한 클래스의 이름은 constructor 와 동일하다.

// Foo는 사실 생성자 함수(constructor)이다.

console.log(Object.getPrototypeOf(foo).constructor === Foo); // true
new 연산자와 함께 호출한 Foo 는 클래스 이름이 아니라 constructor(생성자) 이다

 

3. new 연산자를 사용하지 않고 constructor 를 호출할 수 없다.

class Foo {}

const foo = Foo();
=> typeError

 

constructor

인스턴스를 생성하고 클래스 필드를 초기화하기 위한 특수한 메소드

 

1. constructor 는 클래스 내에 한 개만 존재해야한다.

class Person {

  constructor(name) {
    this._name = name;
  }
}


const me = new Person('Lee');
console.log(me);
=> Person {_name: "Lee"}​

- 클래스가 2개 이상의 constructor 를 포함하면 문법 에러가 발생한다
- 인스턴스를 생성할 때 new 연산자와 함께 호출한 것이 바로 constructor이다
- constructor의 파라미터로 전달한 값("Lee")은 클래스 필드 (_name) 에 할당한다
- this는 클래스가 생성할 인스턴스를 가리킨다

 

2. constructor 는 생략할 수 있다.

class Foo { }

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

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

- 생략하면 클래스에 constructor(){} 를 포함한 것과 동일하게 동작함 (즉, 빈 객체를 생성)
- 인스턴스에 프로퍼티를 추가하려면 인스턴스를 생성한 이후, 프로퍼티를 동적으로 추가해야함

 

3. 클래스 필드를 초기화해야한다면 constructor 를 생략하면 안된다.

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

const foo = new Foo(1);

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

- constructor 는 인스턴스의 생성과 동시에 클래스 필드의 생성과 초기화를 실행시킨다

 

클래스 필드

- 클래스 내부의 캡슐화 된 변수를 말함
- 데이터 멤버 또는 멤버 변수라고도 부름
- 클래스 필드는 인스턴스의 프로퍼티 또는 정적 프로퍼티가 될 수 있다
- 자바스크립트의 생성자 함수에서 this에 추가한 프로퍼티를 클래스 기반 객체지향 언어에서는 클래스 필드라고 부름

 

1. 클래스 몸체에서 클래스 필드 선언하기

class Foo {
    name = '진이';
    constructor(){

    }
}

const foo = new Foo();

console.log(foo.name)
최신 브라우저(chrome 72 이상) 또는 최신 Node.js(12버전 이상) 에서 실행해야된다고 함.

 

2. constructor 내에서 클래스 필드의 선언과 초기화하기

class Foo {
    constructor(name){
        this.name = name
    }
}

const foo = new Foo("진이");

console.log(foo.name)​

- constructor 내부에서 선언한 클래스 필드는 클래스가 생성할 인스턴스를 가리키는 this에 바인딩한다.
- 클래스 필드는 클래스가 생성할 인스턴스의 프로퍼티가 된다.
- 클래스의 인스턴스를 통해 클래스 외부에서 언제나 참조할 수 있다.

 

정적 메소드 (static)

- 클래스와 연결되어 있지만, 해당 클래스의 특정 인스턴스와 연결되어있지 않은 메소드
- 정적 메소드에는 클래스의 객체가 입력 인수로 필요하지 않음.
- 클래스의 객체를 생성하지 않고 정적 메소드를 호출할 수 있음
- 애플리케이션 전역에서 사용할 유틸리티 함수를 생성할 때 주로 사용

 

1. 정적 메소드는 클래스 이름으로 호출함.

class Foo {
    constructor(prop) {
      this.prop = prop;
    }
  
    static staticMethod(name) {
      return name;
    }
  
    prototypeMethod() {
      return this.prop;
    }
  }
  
  console.log(Foo.staticMethod("유진"));​

- 정적 메소드는 인스턴스로 호출 할 수 없다. (즉 , this 를 사용할 수 없음)
- 일반 메소드 내부에서 this 는 클래스의 인스턴스를 가리킴
- 메소드 내부에서 this 를 사용한다는 것은 클래스의 인스턴스의 생성을 전제로 하는 것
- 정적 메소드는 클래스 이름으로 호출하기 때문에 클래스의 인스턴스를 생성하지 않아도 사용할 수 있음

 

2. 왜 클래스 이름으로 호출할까?

정적 메소드는 생성자 함수의 메소드이고 , 일반 메소드는 prototype 객체 ( 즉, 생성자함수.prototype )의 메소드이기 때문에
class Foo {
    constructor(prop) {
      this.prop = prop;
    }
  
    static staticMethod(name) {
      return name;
    }
  
    prototypeMethod() {
      return this.prop;
    }
  }
  
  console.dir(Foo);

  
  const foo = new Foo(123);

  console.dir(foo)

 

class Foo 를 콘솔에 찍은 모습

 


 

클래스 상속

- 클래스 상속은 코드 재사용 관점에서 매우 유용
- 새롭게 정의할 클래스가 기존에 있는 클래스와 유사하다면, 상속을 통해 그대로 사용하며 다른 점만 구현하면 됨
- 코드 재사용은 개발 비용을 현저히 줄일 수 있는 잠재력이 있으므로 매우 중요함

 

extends 키워드 , super ()

extends 키워드만 사용했을 경우

class Human {
    constructor(name, age){
        this.name = name
        this.age = age
        this.leg = 2
        this.arms = 2
        this.foot = 2
    }
}

class YuJin extends Human {
    constructor(name, age){
        this.pretty = true
        this.cute = true
    }
}

class DongJoo extends Human {
    constructor(name, age){
        this.smile = true
        this.good = true
    }
}

const yujin = new YuJin("유진", 25);
const dongjoo = new DongJoo("김동주", 29);

console.log(yujin.name, yujin.age, yujin.leg, yujin.foot)
console.log(dongjoo.name, dongjoo.age, dongjoo.leg, dongjoo.foot)​
  • extends 키워드는 부모 클래스를 상속받는 자식 클래스를 정의할 때 사용함
  • 부모 클래스 (Human) 을 상속받는 자식 클래스 (YuJin, DongJoo) 라는 의미
* 하지만 이렇게 실행을 하면 아래와 같은 오류 발생
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
( super 메소드를 호출하기 이전에는 this 를 참조할 수 없음 )

 

super()

class Human {
    constructor(name, age){
        this.name = name
        this.age = age
        this.leg = 2
        this.arms = 2
        this.foot = 2
    }
}

class YuJin extends Human {
    constructor(name, age){
        super(name,age)
        this.pretty = true
        this.cute = true
    }
}

class DongJoo extends Human {
    constructor(name, age){
        super(name,age)
        this.smile = true
        this.good = true
    }
}

const yujin = new YuJin("유진", 25);
const dongjoo = new DongJoo("김동주", 29);

console.log(yujin.name, yujin.age, yujin.leg, yujin.foot)
=> 유진 25 2 2
console.log(dongjoo.name, dongjoo.age, dongjoo.leg, dongjoo.foot)
=> 김동주 29 2 2


- 부모 클래스를 참조할 때 또는 부모클래스의 constructor 를 호출 할 때 사용된다.
- 부모 클래스의 constructor 를 호출하면서 동일한 인수를 전달함
- 자식 클래스의 constructor 내부에서 부모 클래스의 constructor 를 호출함 ( 즉 , 부모 클래스의 인스턴스를 생성함 )
- 자식 클래스의 constructor 에서 super() 메소드를 호출하지 않으면 위에 에러코드가 발생하게 된다.

오버라이딩(Overriding)
상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의하여 사용하는 방식이다.
오버로딩(Overloading)
매개변수의 타입 또는 갯수가 다른, 같은 이름의 메소드를 구현하고 매개변수에 의해 메소드를 구별하여 호출하는 방식이다.
자바스크립트는 오버로딩을 지원하지 않지만 arguments 객체를 사용하여 구현할 수는 있다.