기존 ES5 이하 환경의 자바스크립트에서 장황하고 불편했던 클래스 생성 등의 과정이 ES6 이후에 class 문법이 생겨서 한결 편하게 클래스를 만들고 관리할 수 있게 되었습니다. 단, 문법만 개선된 일종의 슈가 신택스(sugar syntax)이며 동작 방식은 ES5 이하와 동일한 프로토타입 기반 방식입니다.

class 는 기존의 객체지향언어(자바 등)과 거의 비슷하게 클래스를 선언할 수 있습니다. 생성자가 포함된 클래스를 만드는 법은 다음과 같습니다. 생성자 내에서 클래스에서 사용할 멤버 필드 및 인스턴스 초기 생성시 해야 될 작업 등을 작성합니다.

class Instrument{
    constructor(name){
        this.name = name
        this.inner = "내부구조"
    }
}

 

약간의 차이점은 있지만 지난 글의 프로토타입 기반 클래스 생성 방식으로 만든 클래스와 거의 동일합니다. 생성자는 반드시 constructor라는 키워드를 사용해 구사합니다. 생성자의 thisnew 키워드를 이용해 만든 인스턴스를 가리킵니다.

참고로 클래스는 호이스팅이 되지 않으므로 인스턴스를 생성하는 부분은 반드시 클래스가 선언된 이후에 행해져야 합니다. 클래스 코드 전에 인스턴스를 생성하려고 하면 다음과 같은 에러가 발생합니다.

Uncaught ReferenceError: Cannot access ‘Instrument’ before initialization

 

다음은 메소드를 만들어 보겠습니다. 방법은 매우 단순합니다. 이름과 파라미터, 중괄호 안에 내용만 작성하면 됩니다.

class Instrument{
    constructor(name){
        this.name = name
        this.inner = "내부구조"
    }
    
    // 메소드
    sound(){
        console.log("this", this)
        return "추상적 소리"
    }
    
    // 메소드(setter/getter)
    setInner(innerStructure){
        this.inner = innerStructure
    }
    
    getInner(){
        return this.inner;
    }
}

 

다음은 상속을 구현해 보겠습니다. 상속은 extends라는 키워드를 사용하면 됩니다. 옆 동네에서 많이 본 키워드 같네요.

class WindInstrument extends Instrument{
    
}

 

이전에는 상속이 깔끔하게 되지 않아 여러 추가 작업이 필요 했던 반면에 ES6 클래스는 extends 키워드를 추가하는 것만으로 생성자를 포함한 모든 상속이 한 번에 이루어집니다. 이번에는 상속받은 생성자에서 추가적으로 내용을 추가하고자 합니다. 생성자에서 super를 사용하면 됩니다.

class WindInstrument extends Instrument{
    constructor(name, key){
        super(name) // Instrument 클래스의 내용을 물려받음
        this.key = key
        if(!key){
            console.warn("key를 지정해야 합니다.")
        }
    }
    
    sound(){
        // super() // super는 생성자에서만 사용 가능
    }
}

 

super는 생성자에서만 사용 가능하며 일반 메소드에서는 사용할 수 없습니다. 또한 모든 멤버 필드는 반드시 super를 사용한 이후에 선언해야 합니다. 이전에 선언하려고 하면 다음과 같은 에러 메시지가 발생합니다. 하지만 멤버 필드를 제외한 작업은 super이전에도 선언 가능합니다. 멤버 필드만 불가능한 이유는 this가 가리키는 주소가 super로 재지정되면서 충돌하는 문제 때문입니다.

constructor(name, key){
    const msg = "super 이전에도 멤버 필드 할당을 제외한 작업은 가능합니다."
    this.field = "field" // 에러 발생
    console.warn(msg) // 가능
    super(name)
    
   ....
}

Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor

 

상속받은 자식 클래스만의 메소드를 만드는 방법은 매우 간단합니다. 자식 클래스에 새로운 메소드를 선언하면 됩니다.

    class WindInstrument extends Instrument{
....
        
        smash(){
            return "옆사람이 쓰러졌네요"
        }
    }

 

자바와 다른 점이라면, 메소드에서 다른 메소드에 접근하고 싶은 경우에는 반드시 this를 경유해서 사용해야 한다는 점입니다.

class WindInstrument extends Instrument {
    .......

    smashAndSound(){
        return `${this.sound()} and ${this.smash()}`
        // "추상적 소리 and 옆사람이 쓰러졌네요"
    }

}

 

static(정적) 메소드라는 것도 클래스와 함께 들어왔습니다. 클래스는 인스턴스에서는 실행되지 않고 오직 클래스 자체에서만 실행됩니다. Instrument 클래스에 callRepair 라는 이름의 정적 메소드를 만들어봅시다.

class Instrument{
    ......

    static callRepair(){
        return "기술자가 오는 중입니다."
    }
}

......

const ins = new WindInstrument("리코더")

WindInstrument라는 클래스를 인스턴스 생성 없이 callRepair를 호출하면 정상적으로 실행이 되지만, 인스턴스인 변수 ins에서 실행하려고 하면 그런 메소드는 없다고 하면서 실행되지 않습니다.  정적 메소드는 인스턴스들을 위한 유틸리티 용도로 많이 사용됩니다. 다음은 MDN에 올라와 있는 예제인데 Point 인스턴스 두 개의 거리를 구하기 위한 Point 클래스의 distance 정적 메소드에 관한 예제입니다.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;

        return Math.sqrt(dx*dx + dy*dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2));

 

다음은 동적 setter/getter입니다. 동적으로 특정 변수를 다루고 싶을 때 사용합니다. 메소드 이름 앞에 set 또는 get을 입력합니다. 정적 메소드에서는 static set 또는 static get을 입력합니다. 같은 이름을 공유하면서 = 연산자만 사용해 값을 다룰 수 있습니다.

class OnlyChickenLover{
    
    set inAndOut(foodName){
        if(foodName == "치킨")
            this.food = "좋아요"
        else
            this.food = "싫어요"
    }
    
    get inAndOut(){
        if(this.food == "좋아요")
            return "good"
        else
            return "bad"
    }
}

문의 | 코멘트 또는 yoonbumtae@gmail.com




0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다