자바스크립트/자바스크립트 정리

[자바스크립트] 클래스란 무엇일까?

JS 의 클래스

JS

자바스크립트는 프로토타입 기반의 객체지향 언어입니다. 대부분의 사람들은 자바스크립트는 객체지향 프로그래밍이 아닌 함수형 프로그래밍 언어로 알고있지만, 사실 자바스크립트는 프로토타입을 기반으로 한 강력한 객체지향 프로그래밍을 제공하기도 하는 멀티 패러다임 언어입니다.

프로토타입이에요.

 

사실 대부분의 사람들은 Java, C++ 과 같은 언어에서부터 시작해 클래스 기반 언어에 익숙합니다. 이런 분들은 프로토타입 기반의 프로그래밍 방식에 어려움을 느낄 수 있어 프로토타입은 JS 를 어렵게 느끼게 만드는 수많은(?) 장벽중 하나의 장벽으로 인식되고는 했습니다.

프로토타입 쉬워요 ^^ 한번 해요 ^^

 

라고 말하는 JS 의 프로토타입 고수(?)가 아닌, 클래스가 익숙한 프로그래머들의 좀 더 빠른 학습을 도와주기 위해 ES6 에서는 클래스가 도입됬습니다. 이를 통해 클래스 기반 객체지향 프로그래밍 언어와 매우 흡사한 새로운 객체 생성 메커니즘 을 구현합니다.

ES6 의 클래스가 도입되기 이전에는 생성자 함수로 프로토타입 기반의 인스턴스를 생성했었습니다. 클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스를 생성합니다. 하지만 정확히 동일한 동작을 하지는 않습니다.

JS 는 암묵적으로 많은 것을 허용합니다. 개발자가 실수한 부분에 대해서는 JS 엔진이 실수를 감싸주기도 하는(사실 이 부분은 많은 문제를 야기합니다. 추후에 더 자세히 알아보도록 하겠습니다.) 손자 손녀를 만난 할머니, 할아버지 같은 언어입니다.

하지만 클래스는 생성자 함수보다 엄격하게 작동합니다. 새로운 기능이 추가된다는 것은, 곧 이전의 문제점을 해결해줄 해결책의 등장이라고도 할 수 있습니다. 이번 글에서는 클래스의 장점과 생성자 함수와의 차이점등을 알아보도록 하겠습니다.

생성자 함수와 클래스의 차이

1. new 연산자

JS 에서는 생성자 함수뿐만 아닌 다른 함수에도 new 연산자를 사용 가능합니다. 이는 생성자 함수가 아닌 함수를 생성자 함수로 호출이 가능하고, 일반 함수에 new 연산자를 사용해 생성자 함수로서 호출이 가능하다는 것을 의미합니다.

클래스는 new 연산자 없이 호출이 불가능합니다. 즉, 생성자 함수의 역할만을 하겠다는 강한 의지(?) 를 보여줌으로서 일반 함수로 호출될 여지를 명확하게 제거해버렸습니다.

철벽

 

2. extends 와 super

클래스는 extends 와 super 키워드를 제공합니다. 이를 통해 클래스의 상속이 가능해집니다. extends 는 상속과 관련된 키워드이고, super 는 수퍼클래스의 constructor 호출과 수퍼클래스의 메서드 호출을 가능하게 하는 키워드입니다. 조금 이따 아래 예제에서 살펴보도록 하겠습니다.

3. 클래스 호이스팅

클래스는 let, const 와 마찬가지로 호이스팅이 발생하지 않는 것처럼 동작합니다. 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이 일어나고 함수 표현식으로 정의된 생성자 함수는 변수 호이스팅이 발생하지만, 클래스는 호이스팅이 일어나나 TDZ 에 존재하므로 참조시 에러가 발생하게 됩니다.

console.log(Person);
// ReferenceError: Cannot access 'Person' before initialization  class Person {}

4. strict mode

클래스에 내부에 있는 모든 코드들을 strict mode 가 지정됩니다. 이를 해체할 수 없으며 그렇기 때문에 생성자 함수보다 더 엄격하게 코드를 관리하게 됩니다.

문법적 설탕(synatic sugar) 이라는 단어가 있습니다. 이는 컴퓨터 사이언스에서 표현을 쉽게 만들어주고 가독성을 높여 개발자들의 사용성을 높이는 문법을 이야기합니다. 그렇다면 클래스는 함수 생성자 함수의 문법적 설탕이라고 할 수 있지 않을까요?

문법적 설탕일꺼 같아요!

 

하지만 이 경우 일반적으로 내부 동작이 동일하게 동작하는 코드에 한해서 문법적으로 사용성이 높아지는 경우를 이야기합니다. 클래스는 프로토타입 기반의 객체 생성 패턴을 보다 더 쉽게 구현할 수 있도록 해줍니다. 하지만 내부 동작이 생성자 함수와 동일하게 동작하지 않으므로 새로운 객체 생성 메커니즘 이라고 보는것이 맞다고 생각합니다.

그렇군요

 

클래스 만들어 보기

1. constructor

클래스의 몸체에서는 constructor 가 생성자 역할을 합니다. constructor 는 인스턴스를 생성, 초기화 하는 특수한 메서드이므로 이름 변경이 불가능합니다. 아래와 같은 코드로 클래스를 만들고 인스턴스를 생성하는 것이 가능합니다.

class Person {
    // 생성자 역할을 합니다.
    constructor(name) {
        // 인스턴스 생성 및 초기화 진행이 가능합니다.
        this.name = name;
    }
}
// 클래스를 호출해서 인스턴스를 만들었습니다. 
const me = new Person('Wonjae');

constructor 는 이처럼 생성자 함수와 유사하지만 몇 가지 차이가 있습니다.

  1. constructor 는 클래스 내에 최대 1 개만 존재 가능합니다. 2개 이상의 constructor 를 포함시 문법 에러가 발생합니다.
  2. 생략이 가능합니다. 생략시에는 빈 constructor, 즉 빈 객체가 생성됩니다.
  3. return 문으로 객체를 반환하면 반환한 객체가 할당됩니다. (원시값 반환은 생략!)

2. prototype method

만약 프로토타입으로 메서드를 만들어주지 않는 경우에는 인스턴스가 생성될 때마다 계속해서 메서드를 생성해줘야 합니다. 클래스는 보다 더 간결하게 프로토타입 메서드를 생성하는 것이 가능합니다.

class Person {
    // 생성자 역할을 합니다.
    constructor(name) {
        // 인스턴스 생성 및 초기화 진행이 가능합니다.
        this.name = name;
    }
    // 프로토타입 메서드
    returnName() { console.log(`I'm $`); }
}
// 클래스를 호출해서 인스턴스를 만들었습니다.
const me = new Person('Wonjae');
// 프로토타입 메서드를 호출했습니다.
me.returnName(); // I'm Wonjae

이를 통해 인스턴스는 프로토타입 메서드를 상속받아 사용하는 것이 가능합니다. 즉, 프로토타입 체인은 클래스에 의해서 생성된 인스턴스에서도 동일하게 적용된다는 점을 알 수 있습니다. 이는 클래스도 결국은 생성자 함수와 마찬가지로 프로토타입 기반의 객체 생성 메커니즘이라는 것을 알 수 있습니다.

3. 정적 메서드

정적 메서드는 인스턴스를 생성하지 않고도 호출이 가능한 메서드입니다. 클래스에서는 아래와 같이 정적 메서드(클래스 메서드)를 만들어 줄 수 있습니다.

class Person {
    // 생성자 역할을 합니다.   
    constructor(name) {
        // 인스턴스 생성 및 초기화 진행이 가능합니다
        .this.name = name;
    }
    // 프로토타입 메서드
    returnName() { console.log(`I'm $`); }
    // 정적 메서드
    static sayHello() { console.log('Hello!'); }
}
// 클래스를 호출해서 인스턴스를 만들었습니다.
const me = new Person('Wonjae');
// 프로토타입 메서드를 호출했습니다.
me.returnName();
// I'm Wonjae  
Person.sayHello(); // Hello!

정적 메서드는 클래스에 바인딩된 메서드입니다. 클래스는 함수 객체입니다. 그러므로 본인 소유의 프로퍼티와 메서드를 가질 수 있습니다. 클래스는 인스턴스와 달리 별도의 생성 과정이 필요없기 때문에 클래스로 호출이 가능합니다.

표준 빌트인 객체들은 다양한 정적 메서드를 가지고 있습니다. 이는 애플리케이션 전역에서 사용되는 유틸리티 함수들입니다. 클래스를 이용하면 이처럼 생성자 함수를 하나의 네임스페이스로 사용하는 것이 가능합니다. 이는 이름의 충돌 가능성을 줄여주고 관련 함수의 구조화가 가능합니다.

정적 메서드는 유틸리티 함수를 전역 함수로 정의하지 않고 메서드로 구조화 할 때 유용합니다.

상속을 통해 클래스 확장

프로토타입 기반의 상속은 프로토타입 체인으로 다른 객체의 자산을 상속받지만 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장하는 것을 의미합니다.

상속!

Bird 클래스는 상속을 통해서 Animal 클래스의 속성을 그대로 사용합니다. 또한 자기만의 고유한 속성을 추가하는 것도 가능합니다. 이는 코드 재사용의 관점에서 매우 유용합니다. 이는 클래스의 문법인 extends 로 구현이 가능합니다.

// 부모 클래스인 Animal class 
class Animal {
    constructor(weight, weight) {
        this.height = height;
        this.weight = weight;
    }
}
// 상속을 통해 Animal 클래스를 확장할 수 있습니다.
class Bird extends Animal {
    fly() {
        return 'fly';
    }
}

한가지 주의할 점은, 수퍼 클래스인 Animal 을 상속 받아 사용하므로 (정확히는 수퍼 클래스인 Animal 의 constructor 가 객체를 만들어 서브 클래스에게 전달합니다.) 서브 클래스의 constructor 내부에서는 꼭 super() 를 불러줘야 합니다.

constructor(width, height, color) {
    // super 클래스에게서 width 와 hegiht 를 상속받는 경우입니다.
    super(width, height);
    // 서브 클래스 자체에서 필요한 프로퍼티를 초기화 시켜줍니다.
    this.color = color;
}

만약 super 클래스에게서 그대로 상속을 받는다면 constructor 를 생략할 수 있습니다. 하지만 서브 클래스에서도 초기화를 진행하고 싶다면 꼭 super() 를 불러주고, 매개 변수에 전달할 인수도 필요합니다.

constructor(width, height, color) {
    // super 클래스에게서 width 와 hegiht 를 상속받는 경우입니다
    super(width, height);
    // 서브 클래스 자체에서 필요한 프로퍼티를 초기화 시켜줍니다.
    this.color = color;
}
// 상속 받는 매개 변수에 대한 인수도 같이 전달해줘야 합니다.
const bird = new Bird(2, 4, 'red');
}

이러한 방식을 통해 ES6 에서 클래스를 사용한 객체 지향 프로그래밍이 가능해졌습니다. 이는 ES5 에서 보다 더 간결하고 편하게 코드를 작성할 수 있게 해줄 뿐만 아니라, 일반 함수로의 호출을 막아주는 등 엄격하면서도 견고하고 명료합니다.

보다 더 자세한 내용은 아래 링크에서 확인 가능하고, 저 또한 내용들을 공부하며 정리하기 위한 목적으로 글을 남기는 것을 알려드립니다. 읽어주셔서 감사합니다!

출처 : https://poiemaweb.com/es6-class