해당 포스팅은 위키북스의 모던 자바스크립트 Deep Dive라는 책을 독학하며 기록하는 글입니다.

ES6에서 도입된 클래스는 기존 프로토타입 기반 객체지향 프로그래밍과 같이 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않으며, 생성자 함수보다 엄격하며 생성자 함수에서 제공하지 않는 기능도 제공한다.

다음은 기존의 생성자 함수를 이용한 방식과 클래스 방식과의 몇 가지 차이점이다.

  1. 클래스는 new 연산자 없이 호출하면 에러가 발생한다.
  2. 클래스는 상속을 지원하는 extends와 super 키워드를 제공한다.
  3. 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다.
  4. 클래스 내의 모든 코드는 암묵적으로 strict mode가 적용된다.
  5. 클래스의 모든 프로퍼티들은 열거되지 않는다.

클래스 정의와 인스턴스의 생성

클래스의 정의는 다음과 같이 간단하다.

class Person { }

클래스는 함수이다. 따라서 값처럼 사용할 수 있는 일급 객체이며 일급객체의 특징 또한 모두 가지고 있다.

클래스를 사용해서 인스턴스를 생성하는 법은 생성자 함수와 같이 new 키워드를 같이 사용하면 된다.

class Person { }

const man = new Person();

클래스의 구성

클래스 몸체에는 0개 이상의 메서드를 선언할 수 있으며 정의할 수 있는 메서드는 constructor(생성자), 프로토타입 메서드, 정적 메서드 세 가지가 있다.

  • constructor : constructor는 생성자 메서드로 인스턴스를 생성하고 초기화하기 위한 특수한 메서드이다. constructor 내부의 this는 생성자 함수와 같이 클래스가 미래에 생성할 인스턴스를 가리키게 되며 이를 사용해 인스턴스의 프로퍼티를 초기화할 수 있다.
    몇 가지 특징을 가지고 있는데 다음과 같다.
  1. 한 클래스 내에 두 개 이상 존재할 수 없다.
  2. 생략할 수 있다. (암묵적으로 빈 constructor가 생성된다)
  3. 별도의 반환문(return)을 갖지 않는 것이 좋다.
  4. 별도로 객체를 반환하게 되면 초기화한 객체가 아닌 해당 객체가 this에 바인딩되며, 원시값을 반환하게 되면 무시하고 return문이 없는 것처럼 constructor가 생성한 인스턴스 객체가 반환된다.
class Person {
  constructor(name) {
    this.name = name;
  }
}

const man = Person("Han");
console.log(man.name);  // "Han"
  • 프로토타입 메서드 : 생성자 함수를 통해 프로토타입 메서드를 만들기 위해서는 prototype이라는 키워드를 함수명과 메서드 사이에 써줬어야 했다. 하지만 클래스에서는 그냥 내부에 함수를 생성하면 프로토타입 메서드가 된다.
    클래스는 생성자 함수이므로 생성과 동시에 프로토타입 객체를 만들어내고 이는 프로토타입 객체에 속하게 된다. 따라서 클래스내에서 프로토타입 메서드를 만들면 해당 메서드는 해당 클래스의 프로토타입 객체의 메서드로 등록이 된다.
class Person {
  sayHi() {
    console.log("Hi");
  }
}

cosnt man = new Person();
man.sayHi();  // "Hi"
  • 정적 메서드 : 이전에 공부한 것과 같이 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드로 프로토타입 메서드를 만드는 방식과 마찬가지이되 앞에 static이라는 키워드를 붙혀주면 해당 메서드는 정적 메서드가 된다.
    정적 메서드는 해당 클래스의 프로토타입 객체에 등록되지 않고 클래스 자체에 등록되며 따라서 해당 클래스를 통해 생성한 인스턴스에서 정적 메서드를 호출할 수 없다. (클래스를 통해 호출할 수 있다.)
class Person {
  static sayHi() {
    console.log("Hi");
  }
}

cosnt man = new Person();
man.sayHi();  // TypeError: man.sayHi is not a function
Pserson.sayHi();  // "Hi"

다음은 프로토타입 메서드와 정적 메서드의 차이이다.

  1. 둘은 각자가 속해있는 프로토타입 체인의 위치가 다르다.
  2. 프로토타입 메서드는 인스턴스를 통해, 정적 메서드는 클래스를 통해 호출한다.
  3. 프로토타입 메서드는 인스턴스의 프로퍼티를 참조할 수 있지만, 정적 메서드는 참조할 수 없다.

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

클래스가 인스턴스를 생성하는 과정은 다음과 같다.

  1. 클래스가 new 키워드와 같이 호출이 되면 constructor가 실행되기 이전에 암묵적으로 빈 객체 {}가 생성이 된다. 그리고 해당 빈 객체에 this가 바인딩된다.

  2. 그 다음 constructor 메서드가 실행되어 객체를 초기화하는데, constructor가 생략된 클래스라면 2번 과정역시 생략되어 그대로 빈 객체를 인스턴스로 반환하게 된다.
    constructor가 생략되지 않고 인스턴스롤 초기화하는 코드가 있다면 해당 코드를 실행시켜 인스턴스를 초기화한다.

  3. 클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적을 반환된다.

프로퍼티

클래스내의 프로퍼티 종류로는 인스턴스 프로퍼티와, 접근자 프로퍼티가 있으며 다음과 같다

  • 인스턴스 프로퍼티 : 인스턴스 프로퍼티는 항상 constructor 메서드 내에서 정의해야 한다. 이는 클래스가 생성할 인스턴스의 프로퍼티를 정의하는 것으로 인스턴스 프로퍼티를 통해 암묵적으로 생성된 빈 객체가 초기화된다.

  • 접근자 프로퍼티 : 접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티이다.
    getter함수와 setter함수로 구성되어 있으며 getter는 인스턴스의 프로퍼티에 접근할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때, setter는 인스턴스 프로퍼티에 값을 할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용된다.
    getter함수는 getter함수로 만들고 싶은 메서드 앞에 get 키워드를 붙혀서 만들고, setter는 set 키워드를 붙혀서 만든다.

class Person {
  constructor(name) {
    this.name = name;
  }
  get UserName() {
    return `${this.name}`;
  }
  set UserName(name) {
    this.name = name;
  }
}

클래스 필드

클래스 필드란 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어이다. 하지만 기본적으로 자바스크립트에서는 인스턴스 프로퍼티를 constructor내에서 this를 통해 프로퍼티를 추가할 수 있는데, 2021년 1월 TC39 프로세스의 stage3에 제안되어 있는 "Class field declarations"를 통해 자바스트립트에서도 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어처럼 클래스 몸체에 바로 정의할 수 있게 되었다. (물론 아직 최신브라우저(Chrome72이상) 또는 최신 Node.js(버전12이상)에서만 가능하다)

class Person {
  name = "Han";
}

const man = new Person();
console.log(man.name);  // "Han"

하지만 몇 가지 제약이나 특징이 있는데 다음과 같다.

  1. 클래스 필드를 사용한 프로퍼티에서는 this의 사용이 불가하다. this는 클래스내의 constructor와 메서드 내에서만 유효하다.
  2. 클래스 필드에 프로퍼티를 추가하고 초기값을 할당하지 않으며 undefined로 초기화된다.
  3. 인스턴스를 초기화할 때 외부에서 받은 값으로 초기화하기 위해서는 반드시 constructor에서 클래스 필드를 초기화해야 한다.
  4. 함수는 일급객체이므로 클래스 필드에 할당할 수 있으나 권장하지 않는다.

private와 static 필드

자바스크립트에서는 다른 클래스 기반 객체지향 언어와 같이 private, public, protected 키워드를 제공하지 않는다. 따라서 모든 인스턴스 프로퍼티는 외부에서 참조할 수 있는 public상태이다.

  • private : 클래스 필드와 마찬가지로 2021년 1월 TC39 프로세스의 stage3에 제안되어 있는 사양을 통해 최신브라우저(Chrome74이상) 또는 최신 Node.js(버전12이상)에서는 private기능을 사용할 수 있는데 이는 프로퍼티의 이름 앞에 #을 붙히는 것이다. 당연히 클래스 내부에서 해당 프로퍼티를 참조할 때도 #를 붙여야 한다.
    private를 사용하고자 할 때는 반드시 constructor가 아닌 클래스 필드를 사용해서 정의해야 하며, 클래스 외부에서 해당 프로퍼티를 참조하는 방법으로는 접근자 프로퍼티를 통해 간접적으로 접근하는 법이 있다.
class Person {
  #name;
  
  constructor(name) {
    this.#name = name;
  }
  get UserName() {
  return this.#name;
}

const man = Person("Han");
console.log(man.#name);  // SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(man.UserName);  // "Han"
  • static : 위에서 말했듯 static 키워드를 메서드에 사용해 정적 메서드를 정의할 순 있었지만, 프로퍼티에 사용해 정적 프로퍼티를 만들 수는 없었다.
    하지만 2021년 1월 TC39 프로세스의 stage3에 제안되어 있는 "Static class features"를 통해 최신브라우저(Chrome72이상) 또는 최신 Node.js(버전12이상)에서는 클래스 필드에서 프로퍼티를 정의할 때 static를 앞에 붙혀 정적 프로퍼티를 만들 수 있다.
class Person {
  static type = "사람";
}

console.log(Person.type);  // "사람"

상속

ES6부터 추가된 클래스를 통해 자바스크립테에서도 이제 extends키워드를 통해 클래스를 이용한 상속이 가능해졌다. 사용법은 다음과 같다.

class Base { }  // 부모 클래스

class Derived extends Base { }  // 자식 클래스
  • 서브 클래스에서의 constructor
    서브 클래스에서 constructor를 생략한다면 암묵적으로 다음 코드가 추가되어 실행된다. super는 수퍼클래스의 constructor를 호출하는 키워드로 ...args를 통해 new 키워드와 함께 클래스를 호출할 때 받은 인수를 그대로 전달한다.
construdctor(...args) {
  super(...args);
}
  • super
    super를 호출하면 수퍼클래스의 constructor를 호출할 수 있고, 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수도 있다.
    super의 사용에 있어서도 몇 가지 유의사항이 있는데 다음과 같다.
  1. 서브클래스에서 constuctor를 사용하는데 super를 호출하지 않는 것은 불가능하다.
  2. 서브클래스에서 super를 호출할 때 수퍼클래스의 constructor에서 초기화하는 인자들은 super의 인자로 넘겨준다.
  3. 그 외의 인자들은 서브클래스의 constructor에서 this를 사용해서 초기화한다.
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  getArea() {
    return this.width * this.height;
  }
  toString() {
    return `width = ${this.width}, height = ${this.height}`;
  }
}

class ColorRectangle extends Rectangle {
  constructor(width, height, color) {
    super(width, height);
    this.color = color;
  }
  
  toString() {
    rreturn super.toString() + `, color = ${this.color}`;
  }
}
  • 표준 빌트인 생성자 함수의 확장
    extends 키워드 앞에는 항상 클래스가 와야 하지만 뒤에는 클래스가 올 수도 있고, 생성자 함수가 올 수도 있다. 따라서 extends를 사용해 표준 빌트인 생성자 함수인 Math나 Array 등을 상속받아 자신만의 클래스를 만들 수 있다.
profile
I Will be Relaxed Person

0개의 댓글