[ES6] 6. 클래스(Class)

ES6에서부터는 ES5까지는 존재하지 않았던 Class가 생겨났다. Java에서의 Class와는 똑같은 기능을 한다고 생각해서는 안된다.
자바스크립트는 기본적으로 Prototype기반의 객체지향 언어다. 즉, ES6Class또한 프로토타입을 기반으로 동작하며 이는 기존의 자바스크립트에서 객체지향적으로 설계할 때의 방식을 좀 더 편하게 보완한 Syntatic Sugar다.

클래스의 정의

//ES5
var Person = (function() {
    //생성자 함수 정의
    function Person(name, job) {
        this.name = name;
        this.job = job;
    }

    Person.prototype.sayInfo = function() {
        console.log('Name : ' + this.name + ', Job : ' + this.job);
    }

    return Person;
}());

var bkJang = new Person('BKJang', 'Developer');

bkJang.sayInfo(); //Name : BKJang, Job : Developer

ES6에서 Class가 생기기 전 우리는 위와 같은 방식으로 생성자 함수와 프로토타입을 이용해 객체지향 프로그래밍을 진행했었다. 위와 같은 코드를 ES6Class를 사용하여 구현하면 아래와 같이 좀 더 간결하게 구현할 수 있다.

//ES6
class Person {
    constructor(name, job) {
        this.name = name;
        this.job = job;
    }

    sayInfo() {
        console.log(`Name : ${this.name}, Job : ${this.job}`);
    }
}

const bkJang = new Person('BKJang', 'Developer');

bkJang.sayInfo(); //Name : BKJang, Job : Developer

클래스는 기본적으로 위와 같이 Class Person {}l으로 정의하며, 흔치는 않지만 const Person = class MyClass {};처럼 함수 표현식으로도 정의 가능하다.

둘의 또 다른 차이점은 생성자 함수를 이용하여 선언하면 window에 할당되지만, Class를 이용하여 선언하면 window에 할당되지 않는다.

또한 Class 안에 있는 코드는 항상 strict mode 로 실행되기 때문에 “use strict” 명령어가 없더라도 동일하게 동작한다.

function Person() {}
class Developer {}

console.log(window.Person); //ƒ Person() {}
console.log(window.Developer); //undefined

인스턴스의 생성과 호이스팅

Class를 사용하여 인스턴스를 생성할 때는 반드시 new를 이용해 호출해야하며 new를 사용하지 않으면 Type Error가 발생한다.

class Foo {

}

const foo = Foo(); //Uncaught TypeError: Class constructor Foo cannot be invoked without 'new'

ES6Classlet, const와 마찬가지로 호이스팅이 일어나지만, 선언이 일어나고 할당이 이뤄지기 전 TDZ(Temporary Dead Zone)에 빠지기 때문에 할당 이전에 호출하면 Reference Error가 발생한다.

const foo = new Foo(); //Uncaught ReferenceError: Foo is not defined

class Foo {

}

constructor

constructor는 인스턴스를 생성하고 Classproperty를 초기화한다. ES5에서는 생성자 함수를 이용해 property를 초기화하고 생성자 함수를 반환함으로써 객체지향을 구현했었다.

Class는 constructor를 반환하며 생략할 수 있다.

const foo = new Foo()와 같이 선언했을 때 FooClass명이 아닌 constructor다.

class Foo {

}

const foo = new Foo();

console.log(Foo == Foo.prototype.constructor); //true

위의 코드에서 볼 수 있듯이 new와 함께 호출한 Fooconstructor와 같음을 확인할 수 있다.

또 확인할 수 있는 것은 class Foo내부에 constructor를 선언하지 않았음에도 인스턴스의 생성이 잘 이뤄지는 것을 볼 수 있다.
이는 Class내부에 constructor는 생략할 수 있으며 생략하면 Classconstructor() {}를 포함한 것과 동일하게 동작하기 때문이다.
즉, 빈 객체를 생성하기 때문에 property를 선언하려면 인스턴스를 생성한 이후, property를 동적 할당해야 한다.

console.log(foo); //Foo {}

foo.name = 'BKJang';

console.log(foo); //Foo {name: "BKJang"}

Class의 property는 constructor 내부에서 선언과 초기화가 이뤄진다.

Class의 몸체에는 메서드만 선언 가능하며, propertyconstructor내부에서 선언하여야 한다.

class Foo {
    name = ''; //Syntax Error
}
class Bar {
    constructor(name = '') {
        this.name = name;
    }
}

const bar = new Bar('BKJang');
console.log(bar); //Bar {name: "BKJang"}

constructor내부에서 선언한 propertyClass의 인스턴스를 가리키는 this에 바인딩 된다.

getter, setter

Class의 프로퍼티에 접근하기 위한 인터페이스로서, gettersetter를 정의할 수 있다.

class Person {
    constructor(name) {
        this.name = name;
    }

    //getter
    get personName() {
        return this.name ? this.name : null;
    }

    //setter
    set personName(name) {
        this.name = name;
    }
}

const person = new Person('BKJang');

console.log(person.personName); //BKJang
person.personName = 'SHJo';
console.log(person.personName); //SHJo

Static 메서드

Class에서는 정적 메서드를 정의할 때, static 키워드를 사용하여 정의한다. 정적 메서드는 인스턴스를 생성하지 않아도 호출가능하며, 인스턴스가 아닌 Class의 이름으로 호출한다. 이와 같은 특징 때문에 애플리케이션을 위한 유틸리티성 함수를 생성하는데 주로 사용한다.

또한 정적 메서드 내부에서는 this를 사용할 수 없다. 왜냐하면 정적 메서드 내부에서는 thisClass의 인스턴스가 아닌 Class자기 자신을 가리키기 때문이다.

class Person {
    constructor(name) {
        this.name = name;
    }

    //getter
    get personName() {
        return this.name ? this.name : null;
    }

    //setter
    set personName(name) {
        this.name = name;
    }

    static staticMethod() {
        console.log(this);
        return 'This is static';
    }
}

console.log(Person.staticMethod()); 
/*
class Person { ... }
This is static
*/

const instance = new Person('BKJang');

console.log(instance.staticMethod()); //Uncaught TypeError: instance.staticMethod is not a function

위에서 볼 수 있듯이 인스턴스로는 Class의 정적 메서드를 호출할 수 없다.

또한 정적 메서드는 prototype에 추가되지 않는다.

console.log(Person.staticMethod === Person.prototype.staticMethod); //false
console.log(new Person().personName === Person.prototype.personName); //true

클래스의 상속

Class를 이용하여 OOP의 특징 중 하나인 상속을 구현할 수 있다.
Class의 상속을 위해서는 extendssuper 키워드에 대해서 알아야 한다.

class Person {
    constructor(name, sex) {
        this.name = name;
        this.sex = sex;
    }

    getInfo() {
        return `Name : ${this.name}, Sex : ${this.sex}`;
    }

    getName() {
        return `Name : ${this.name}`;
    }

    getSex() {
        return `Sex : ${this.sex}`;
    }
}

class Developer extends Person { //extends를 사용하여 Person 클래스 상속
    constructor(name, sex, job) {
        //super메서드를 사용하여 부모 클래스의 인스턴스를 생성
        super(name, sex);
        this.job = job;
    }

    //오버라이딩
    getInfo() {
        //super 키워드를 사용하여 부모 클래스에 대한 참조
        return `${super.getInfo()} , Job: ${this.job}`;
    }

    getJob() {
        return `Job : ${this.job}`;
    }
}

const person = new Person('SHJo', 'Male'); 
const developer = new Developer('BKJang', 'Male', 'Developer');

console.log(person); //Person {name: "SHJo", sex: "Male"}
console.log(developer); //Developer {name: "BKJang", sex: "Male", job: "Developer"}

console.log(person.getInfo()); //Name : SHJo, Sex : Male

console.log(developer.getName()); //Name : BKJang
console.log(developer.getSex()); //Sex : Male
console.log(developer.getJob()); //Job : Developer
console.log(developer.getInfo()); //Name : BKJang, Sex : Male , Job: Developer

console.log(developer instanceof Developer); //true
console.log(developer instanceof Person); //true

위의 소스를 기준으로 중요한 특징을 정리하자면 다음과 같다.(대부분의 객체 지향 언어에서 상속의 특징과 거의 동일하다.)

  • 부모 클래스(슈퍼 클래스)의 메서드를 사용할 수 있다.

  • 부모 클래스의 메서드를 오버라이딩(Overriding)할 수 있다.

  • super 키워드를 통해 부모 클래스의 메서드에 접근할 수 있다.

  • super 메서드(위의 Developer 클래스의 constructor내부에 선언)는 자식 클래스의 constructor 내부에서 부모 클래스의 constructor(super-constructor)를 호출한다.


Reference

Published 7 Feb 2019

BKJang's Devlog