[JS]모자딥 CH.25 클래스

현우.·2024년 11월 25일
post-thumbnail

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

  1. 클래스는 new 연산자 없이 호출시 에러 발생
  2. 클래스는 상속을 지원하는 extends와 super키워드 제공
  3. 클래스는 let,const로 정의한 변수 호이스팅이 일어나는 것처럼 동작해 호이스팅이 발생하지 않는 것처럼 보인다.
  4. 클래스 내의 모든 코드는 암묵적으로 strict mode가 지정되어 실행된다.
  5. 클래스의 constructor,프로토타입 메서드,정적 메서드는 모두 [[Enumerable]] 값이 false.
    즉 열거가 불가능하다.

클래스의 호이스팅

클래스 선언문으로 정의한 클래스는 함수 선언문처럼 런타임 이전에 평가되어 함수 객체를 생성한다.
단, 클래스는 클래스 정의 이전에 참조할 수 없다.

console.log(Person);

class Person {}

클래스 선언문은 let,const 키워드로 선언한 변수처럼 호이스팅이 발생한다.
따라서 선언문 이전에 TDZ에 빠져 호이스팅이 발생하지 않는것처럼 동작한다.

클래스의 인스턴스 생성

클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이기 때문에 반드시 new 연산자와 함께 호출해야한다.

추가로 클래스는 표현식으로 작성할 수 있는데 이때 클래스 표현식에서 사용한 클래스 이름은 외부에서 접근이 불가능하다.

const Person = class MyClass {};

console.log(MyClass); // error
const me = new MyClass(); // error

클래스의 메서드

constructor

인스턴스를 생성하고 초기화하기 위한 특수한 메서드

constructor와 생성자 함수의 차이점

  1. 클래스내에 최대 한개 존재
// error
class Person {
    constructor(){}
    constructor(){}
}
  1. constructor 생략 가능
    constructor를 생략한 클래스는 암묵적으로 생성된 빈 constructor에 의해 빈 객체 생성
class Person {}

const me = new Person();
console.log(me); // Person {}

constructor의 반환문

constructor는 별도의 반환문을 갖지 않아야한다.
constructor는 암묵적으로 this, 즉 인스턴스를 반환하기 때문이다.

만약 this가 아닌 다른 객체를 명시적으로 return하면 인스턴스가 반환되지 못하고 명시된 객체가 반환될 것이다.

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

const me = new Person();
console.log(me); // {}

클래스의 정적 메서드

클래스의 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드이다.

메서드앞에 static 키워드를 붙여 사용한다.

정적 메서드는 클래스에 바인딩된 메서드가 된다.
클래스는 함수 객체이므로 자신만의 프로퍼티,메서드를 소유할 수 있다.

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

  1. 자신이 속해있는 프로토타입 체인이 다르다.
  2. 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.
  3. 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 가능하다.

즉, 메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있으면 프로토타입 메서드로 정의해 this를 사용하고,

인스턴스 프로퍼티를 참조할 필요가 없다면 this 사용없이 정적 메서드로 정의하면 된다.

클래스에서 정의한 메서드 특징

  1. 메서드 축약 표현 사용
  2. 암묵적으로 strict mode로 실행.
  3. [[Enumerable]] 값이 false. 즉 열거 불가능.
  4. 내부 메서드 [[Constructor]]를 갖지 않는 non-constructor.
    따라서 new 연산자와 함께 호출 불가능.
    (constructor는 클래스에서 단 한개 존재한다.)

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

1. 인스턴스 생성및 this 바인딩

new 연산자와 함께 클래스를 호출시 암묵적으로 빈 객체가 생성되고,
이 빈 객체가 바로 클래스가 생성한 인스턴스가 된다.
인스턴스는 this에 바인딩된다.

2. 인스턴스 초기화

this에 바인딩 되어있는 인스턴스를 초기화한다.
인스턴스의 프로퍼티를 추가해 초기화해준다.

3. 인스턴스 반환

완성된 인스턴스가 바인딩된 this를 반환한다.

class Person {
  constructor(name) {
    console.log(this); // {}
    this.name = name; // this에 바인딩된 인스턴스 초기화
  }
}

접근자 프로퍼티

접근자 프로퍼티(getter함수,setter함수)는 클래스에서도 사용할 수 있다.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(" ");
  }
}

getter, setter 이름은 인스턴스 프로퍼티처럼 사용한다.


const me = new Person("kim", "hw");

me.fullName = "hw kim";
console.log(me.fullName); //hw kim

getter는 참조시에 내부적으로 getter가 호출된다.
setter는 할당시에 내부적으로 setter가 호출된다.

클래스의 메서드는 기본적으로 프로토타입 메서드가 된다.
따라서 접근자 프로퍼티 또한 인스턴스 메서드가 아니라 프로토타입 메서드이다.

클래스 필드란?

클래스 필드란 클래스기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어

자바스크립트의 클래스 몸체에는 메서드만 선언할 수 있었다.
따라서 몸체에 클래스 필드를 선언하면 에러가 발생한다.

그러나 최신 브라우저나 Node.js에서는 클래스 필드를 몸체에 정의할 수 있다.

class Person {
  name = "lee";
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

const me = new Person("kim", "hw");

console.log(me.name); // lee

주의할 점

  1. 몸체에 클래스 필드를 정의할때 주의할 점은 this에 클래스 필드를 바인딩해서는 안된다.
    this는 오로지 constructor 메서드 내에서만 유효하다.

  2. 클래스 필드를 참조할 경우 반드시 this를 사용해야한다.

  3. 클래스 필드에 초기값 할당하지 않으면 undefined 반환

class Person {
  name = "lee";
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    console.log(this.name);
  }
}

const me = new Person("kim", "hw"); // lee
  1. 클래스 필드에는 변수뿐만아니라 함수도 할당가능하다.
    이경우 함수는 프로토타입 메서드가 아닌 인스턴스 메서드가 된다.
    모든 클래스 필드는 인스턴스 프로퍼티가 되기 때문이다.
    따라서 클래스 필드에 함수를 할당하는 것은 권장되지 않는다.
class Person {
  name = "lee";
  getName = function () {
    return this.name;
  };
}

const me = new Person();

console.log(me.getName());

private 필드

js는 클래스 기반 객체지향 언어에서 제공하는 접근제한자를 지원하지 않았다.

그러나 2021년에 private 필드를 정의할 수 있게 되었다.

private을 선언하는 방법은 선두에 #을 붙여주면 된다.

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

const me = new Person("khw");

console.log(me.#name); // 참조 에러
        

private 필드는 반드시 클래스몸체 내부에서 정의해야한다.

참고📌) TS는 클래스 기반 객체지향 언어가 지원하는 접근제한자인 public,private,protected를 모두 지원한다.


클래스의 상속

프로토타입 상속과 상속에 의한 클래스 확장은 다른 개념이다.

프로토타입 기반 상속은 프로토타입 체인으로 다른객체의 자산을 상속받는 것이지만,
상속에 의한 클래스 확장은 새로운 클래스를 확장하여 정의하는 것이다.

클래스와 생성자함수는 인스턴스를 생성할 수 있는 함수라는 점에서는 유사하다.
그러나 클래스는 상속을 통해 기존 클래스를 확장할 수 있는 문법이 기본적으로 제공되지만
생성자 함수는 그렇지 않다.

extends 키워드

extends 키워드를 사용하여 클래스를 확장할 수 있다.

extends 키워드의 역할은 부모 클래스와 자식 클래스 간의 상속관계를 설정하는 것이다.
클래스도 프로토타입을 통해 상속관계를 구현한다.

부모 클래스와 자식 클래스는 인스턴스의 프로토타입 체인뿐 아니라 클래스간의 프로토타입 체인도 생성하기 때문에 프로토타입 메서드, 정적 메서드 모두 상속이 가능하다.


super 키워드

super 키워드는 함수처럼 호출되거나 this와 같이 식별자처럼 참조할 수 있는 키워드이다.

super 호출시 -> 부모 클래스의 constructor를 호출한다.
super 참조시 -> 부모 클래스의 메서드를 호출 할 수 있다.

super 호출

constructor 생략시 암묵적으로 정의된다.

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

class Derived extends Base {

  //   constructor(...agrgs) {
  //     super(...args);
  //   }
}
const derived = new Derived(1, 2);
console.log(derived);

만약 자식 클래스에서 추가한 프로퍼티를 갖는 인스턴스를 생성하려면 constructor를 생략할 수 없다.

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

class Derived extends Base {
  constructor(a, b, c) {
    super(a, b);
    this.c = c;
  }
}
const derived = new Derived(1, 2, 3);
console.log(derived);

super 호출시 주의할 부분

  1. 자식 클래스에서 constructor를 생략하지 않았다면 constructor는 반드시 super를 호출해야한다.
  1. 자식 클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수없다.

  2. super는 반드시 자식 클래스의 constructor에서만 호출한다.

super 참조

메서드 내에서 super를 참조하면 부모 클래스의 메서드를 호출할 수 있다.

1.자식 클래스의 프로토타입 메서드에서 super의 메서드는 부모 클래스의 프로토타입 메서드를 가린킨다.

class Base {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }
  sayA() {
    return ` Hi ${this.a}`;
  }
}

class Derived extends Base {
  constructor(a, b, c) {
    super(a, b);
    this.c = c;
  }
  sayA() {
    return `${super.sayA()} How Are You?`;
  }
}
const derived = new Derived(1, 2, 3);
console.log(derived.sayA());

super 참조가 동작하려면 super가 참조하고 있는 메서드가 바인딩된 객체의 프로토타입을 찾을 수 있어야 한다.

이를 위해서 메서드는 내부슬롯 [[HomeObject]]를 가지고 자신을 바인딩하고 있는 객체를 가리킨다.
주의) ES6의 축약표현으로 정의된 메서드만이 내부 슬롯 [[HomeObject]]를 가진다.

2.자식 클래스의 정적 메서드에서도 super가 참조한 메서드는 부모 클래스의 정적 메서드를 가리킨다.

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

1. 자식 클래스의 super호출

자식 클래스는 자신이 직접 인스턴스를 생성하지 않고 부모 클래스에게 인스턴스 생성을 위임한다.
이것이 바로 자식 클래스의 constructor에서 super를 호출해야하는 이유이다.

2. 부모 클래스의 인스턴스 생성 및 this 바인딩

constructor 내부 코드 실행 전에 암묵적으로 빈 객체를 만든다.
그리고 이 빈 객체는 this에 바인딩된다.

인스턴스는 부모 클래스가 생성한 것이지만 실제로 new연산자와 함께 호출된 것은 자식 클래스이다.
따라서 인스턴스는 자식 클래스가 생성한 것으로 처리된다.

3. 부모 클래스의 인스턴스 초기화

부모 클래스의 constructor가 실행되면 this에 바인딩되어있는 인스턴스를 초기화한다.

즉, 인스턴스에 프로퍼티를 추가하고, 인수로 전달받은 초기값으로 프로퍼티를 초기화한다.

4. this 바인딩

super 호출이 종료되면 자식 클래스 constructor로 돌아온다.
이때 super가 반환한 인스턴스가 this에 바인딩된다.
자식 클래스는 별도의 인스턴스를 안 만든다.

5. 자식 클래스 인스턴스 초기화

자식 클래스의 construcor가 실행되어 this에 바인딩된 인스턴스에 프로퍼티를 추가및 초기화해준다.

6. 인스턴스 반환

클래스의 모든 처리 후 바인딩된 this는 암묵적으로 반환된다.

profile
학습 기록.

0개의 댓글