클래스

Kyung yup Lee·2021년 6월 1일
0

자바스크립트

목록 보기
9/12

클래스는 ES6에서 새로 도입된 객체 생성 방법이다. 자바스크립트는 기본적으로 프로토타입 기반 객체지향 언어이기 때문에, 클래스 기반 객체지향 프로그래밍 언어에 익숙한 개발자는 접근하기 어려웠었다. 생성자 함수와 비슷한 기능을 제공하지만 완전히 같지는 않다. 때문에, 클래스는 생성자 함수의 다른 문법이라기 보다는, 새로운 객체 생성 메커니즘으로 봐야 한다.

클래스

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

  1. 클래스를 new 연산자 없이 호출하면 에러가 발생한다. 생성자 함수는 new 없이 호출하면 일반함수로 호출됐었다.
  2. 클래스는 상속을 지원하는 extends 와 super 키워드를 지원한다.
  3. 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다(TDZ). 함수 선언문은 함수 호이스팅이 발생하고, 함수 표현식은 변수 호이스팅이 발생하는 것과는 차이가 있다.
  4. 클래스 내부는 자동으로 strict 모드로 강제된다.
  5. 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[enumerable]] 이 false이다.

클래스 정의

생성자 함수와 마찬가지로 파스칼 케이스를 사용해서 정의하는 것이 일반적이다.

클래스는 표현식으로 변수에 할당도 가능한 1급 객체이다. 즉, 클래스는 함수다.

class Person{
 constructor(name){
  this.name = name;  // 초기화
 }
  
  sayHi(){
   console.log(`Hi! My name is ${this.name}); // 프로토타입 메서드
  }
  static sayHello(){
    console.log("hello"); // 정적 메서드
  }
}

클래스에 대하여

클래스 호이스팅

클래스는 함수라 했다. 그러므로 클래스도 함수와 마찬가지로 런타임 이전에 평가되어 함수 객체를 생성한다. 또한 생성자 함수가 생성될 때와 마찬가지로 프로토타입이 만들어져서 바인딩된다.
클래스는 TDZ 를 갖는다. 그래서 호이스팅이 발생하지 않는 것처럼 보이지만 호이스팅이 발생한다.

인스턴스 생성

클래스는 생성자 함수처럼 new 를 통해 인스턴스를 생성한다고 했다.

class Person{}

const me = new Person();

메서드

클래스에서 선언할 수 있는 메서드는, constructor, 프로토타입 메서드, 정적 메서드 세 가지가 있다.

constructor

constructor는 인스턴스를 만들었을 때, 해당 인스턴스를 초기화 시켜주기 위한 특수 메서드이다. constructor는 인스턴스가 만들어졌을 때, 빈 객체를 생성한 후 this 바인딩을 한다. 이 빈 객체는 나중에 인스턴스로 반환되게 될 것이다. constructor 내부에 초기화 값이 있다면

const Person {
  constructor(name){
   this.name = name 
  }
}

새로 만들어지는 인스턴스에 프로퍼티로 해당 인수를 추가하게 된다. 생성자 함수와 실로 똑같지 않을 수 없다. constructor는 한 개만 만들 수 있다. 그리고 초기화가 끝나면 만들어둔 this에 바인딩된 객체를 암묵적으로 return 한다. 그래서 constructor 내부에는 return 을 만들면 안된다.

프로토타입 메서드

클래스에서의 프로토타입 메서드는 생성자 함수와 다르게 클래스안에 선언하기만 하면 프로토타입 메서드로 동작한다. 이 프로토타입 메서드는 생성자 함수와 쌍을 이뤘던 프로토타입의 메서드로 작동하고, 모든 프로토타입 체인이 생성자 함수에서 만들어지는 것과 동일하다.

정적 메서드

정적 메서드 또한 생성자 함수에서와 마찬가지로 동작한다. 하지만 약간의 문법적 차이가 있는데,

function Person(name){
 this.name = name; 
}
Person.sayHi = function(){};

같이 생성자 함수에 직접 함수를 추가해주었어야 하지만, 클래스의 경우

class Person{
 static sayHi(){
   
 }
}

처럼 static 을 붙이면 정적 메서드로 동작한다. 정적 메서드는 인스턴스화 하지 않아도 호출할 수 있고, 프로토타입 체인에 존재하지 않기 때문에, 인스턴스에서 호출할 수 없다.

정적 메서드와 프로토타입 메서드의 차이

  1. 정적 메서드와 프로토타입 메서드는 프로토타입 체인이 다르다.
    정적 메서드는 클래스에 선언되었기 때문에, Function 프로토타입 체인을 따른다. 반면 프로토타입 메서드는 인스턴스의 프로토타입에 만들어져 기본적인 객체 프로토타입 체인을 따른다.
  2. 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.
  3. 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만, 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.

클래스 정의 메서드의 특징

  1. function 키워드를 생략한 축약 메서드 표현 사용
  2. 클래스에 메서드를 정의 할 때는 콤마가 필요없음.
  3. 암묵적인 strict 모드
  4. for ... in 문이나, Object.keys 메서드 등으로 열거가 불가능하다.
  5. 내부 메서드 Construct 가 없다. 즉 non-constructor이다.

프로퍼티

클래스로 만들어지는 인스턴스의 프로퍼티는 constructor 내부에서 정의해야 된다.
constructor 코드가 실행되기 이전에 클래스가 정의되면서 constructor를 통해 만들어진 빈 객체와 this 가 바인딩되어있다고 했다. 때문에 constructor 내부에서만 해당 객체의 프로퍼티를 선언할 수 있다.

접근자 프로퍼티

위에 예외가 있는데, 접근자 프로퍼티이다. 접근자 프로퍼티는 프로퍼티이지만, 실제로 내부에서 프로토타입 메서드로 작동하게 된다. 때문에, 접근자 프로퍼티는 프로토타입에 메서드로 저장되게 되고, constructor 밖에서 선언될 수 있다.

class Person{
  constructor(fName, lName){
   this.fName = fName;
   this.lName = lName;
  }
 get fullName(){
  return this.fName; 
 }
}

위 처럼 접근자 프로퍼티를 생성자 함수와 같은 방식으로 만들면 프로토타입 메서드로 작동한다.

상속

사실 클래스가 도입된 이유 중 하나는 프로토타입을 통해 객체 상속관계를 나타내는데에는 한계를 어느정도 느꼈기 때문이 아닐까 싶다. 하지만 이렇게만 생각하면 안되는게 프로토타입의 상속은 실제 존재하는 객체가 프로토타입 체인을 통해 더 많은 메서드나 프로퍼티를 사용하는 것을 가능하게 만들어주는 것이지만, 클래스에서의 상속은 클래스를 상속받아 템플릿을 확장하기 위한 목적이기 때문이다. 결과는 크게 다르지 않겠지만, 개념적인 부분에 차이가 있다.

extends

클래스에서 상속을 구현하기 위해서는 extends 키워드를 사용하여 상속받을 클래스를 정의한다.
자바스크립트는 클래스 기반 언어가 아니기 때문에, 클래스로 상속관계를 정의하더라도 프로토타입 상속 관계가 만들어진다.

동적 상속

extends 키워드는 클래스 뿐만 아니라 생성자 함수도 상속 받을 수도 있다. 다만 상속받는 주체는 클래스여야 한다.

서브 클래스의 constructor

super 키워드

super 키워드는 함수처럼 호출할 수도 있고, this 처럼 식별자로 참조할 수도 있다. super를 함수처럼 호출하면 수퍼 클래스의 constructor를 호출한다. super를 참조하면 수퍼클래스를 참조하여, 수퍼 클래스의 메서드를 호출할 수 있다.

super 호출

super를 호출하면 수퍼 클래스의 constructor를 호출한다 했다. 하위 클래스를 인스턴스화 하면서 생성자를 호출할 필요가 없는 경우 super() 를 호출할 필요가 없다. 하지만 만약 아래 예제와 같은 상황이라면,

class Base{
  constructor(a, b){
    this.a = a;
    this.b = b;
  }
}

class Derived extends Base{
 constructor(c)
  super();
  this.c = c;
 }
}
const d = new Derived(1,2,3);

반드시 super()를 호출해주어야 한다. 또한 this 바인딩 보다 반드시 super()를 호출해야 한다. 이는 이유가 있다. 밑에서 설명할 예정.

super 참조

super는 식별자로 사용될 수 있고, 이 경우에는 수퍼클래스의 메서드를 호출할 수 있다 했다.

super 식별자를 사용하지 않을 경우 이 내용이 굉장히 복잡해진다.

class Base {
 constructor(name){ 
   this.name = name;
 }
}
sayHi(){
 return `hi. ${this.name}`; 
}

class Derived1 extends Base{
 sayHi(){
   return `${super.sayHi()}. how do?`;
 }
}

class Derived2 extends Base{
 sayHi(){
  const __super = Object.getPrototypeOf(Derived2.prototype);
   return `${__super.sayHi.call(this)} how do?`;
 }
}

딱 봐도 아래 내용은 직관적이지 않고 어려워 보인다. 둘의 내용 모두 수퍼 클래스의 메서드를 호출하여 사용하는 내용이다. 아래의 클래스(Derived2) 를 보면 super 키워드를 사용하지 않기 때문에 상위 프로토타입에 접근하여, 해당 프로토타입의 메서드를 받아와야 한다. __super 키워드에는 Base.prototype 이 저장되게 된다. Base.prototype에는 sayHi 메서드가 존재하지만 문제는 Base의 name 프로퍼티를 초기화 해주지 못했다. 그렇기 때문에, 현재 인스턴스의 this를 바인딩 해주어서 수퍼클래스의 this를 강제로 바인딩해줄 필요가 있다. super 키워드를 사용할 경우 자동으로 constructor를 따라 올라가면서 모든 this 바인딩을 해결해주지만 그렇게 하지 못하면 이런 번거로운 과정을 거쳐야 하는 것이다.

상속 클래스의 인스턴스 생성 과정

서브클래스의 super 호출

이전에 서브클래스의 constructor는 초기화 이전에 super()를 반드시 먼저 호출해야 한다고 했다. 이렇게 하는 이유는 서브클래스가 인스턴스를 생성하지 않고, 해당 인스턴스 생성을 수퍼클래스에 위임하기 때문이다. 즉 서브클래스의 constructor 내부에서 super()를 만나면 인스턴스 생성(this 생성)을 상위클래스에 위임하여, 상위 클래스에서 먼저 인스턴스 생성과 this 바인딩이 일어나는 것이다.

상위 클래스의 인스턴스 생성과 this 바인딩

수퍼 클래스에 인스턴스 생성이 위임되었다. 수퍼클래스에서도 constructor가 실행되기 이전에 빈 객체를 만들고 this 바인딩을 한다. 중요한 것은 인스턴스를 생성한 주체는 서브클래스라는 것이다. "위임" 이라는 표현을 사용했다. 위임이라는 것은 주체가 바뀌지 않는다. 주체는 서브클래스이고 생성만 부모클래스가 해주는 것이다.

상위 클래스의 인스턴스 초기화

상위 클래스는 constructor를 통해서 만들어진 인스턴스를 초기화 한다.

서브 클래스로 반환

상위 클래스의 초기화가 끝나고 만들어진 인스턴스는 다시 서브 클래스로 반환된다. 그러면 이제서야 서브클래스는 constructor를 이어서 실행해 this에 바인딩 한다.

서브 클래스의 초기화

서브 클래스는 그대로 받아온 인수로 인스턴스를 모두 초기화한다. 그리고 이렇게 상위 클래스를 모두 돌고 온 인스턴스를 반환한다.

결론

이렇게 클래스를 마무리했다. 개인적으로 리액트가 판을 치고 있는 한, 함수형으로 프로그램을 작성하는 트렌드가 이어질거라 생각한다. 때문에 순수함수와 일급 객체등의 함수형 프로그래밍을 하는 방법을 잘 알아야 하지만, 객체 지향 프로그래밍이 인간 친화적인 관점으로 프로그램을 짜기 때문에, 절대 죽지는 않을 것이라 생각하고, 클래스를 통한 자바스크립트 사용에도 익숙해질 필요가 있다고 생각한다.

profile
성장하는 개발자

0개의 댓글