JS (25) - 클래스

최조니·2022년 7월 23일
0

JavaScript

목록 보기
22/36

25.1 새로운 객체 생성 메커니즘, 클래스

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

  • 클래스new 연산자 없이 호출하면 에러 발생
    생성자 함수new 연산자 없이 호출하면 일반 함수로서 호출

  • 클래스는 상속을 지원하는 extendssuper 키워드 제공
    생성자 함수는 지원 X

  • 클래스는 호이스팅이 발생하지 않는 것처럼 동작
    함수 선언문으로 정의된 생성자 함수함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수변수 호이스팅 발생

  • 클래스 내의 모든 코드에는 암묵적으로 strict mode가 지정되어 실행되며 strict mode를 해제할 수 없음
    생성자 함수는 암묵적으로 strict mode가 지정되지 않음

  • 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumberable]] 값이 false


25.2 클래스 정의

  • class 키워드를 사용하여 정의

  • 파스칼 케이스를 사용하는 것이 일반적이지만, 사용하지 않아도 에러 발생 X

  • 클래스를 표현식으로 정의할 수 있음

// 익명 클래스 표현식
const Person = class {};

// 기명 클래스 표현식
const Person = class MyClass{};
  • 클래스의 일급 객체로서의 특징
    • 무명의 리터럴로 생성할 수 있음 (런타임에 생성 가능)
    • 변수나 자료구조(객체, 배열 등)에 저장할 수 있음
    • 함수의 매개변수에 전달할 수 있음
    • 함수의 반환값으로 사용할 수 있음
class = Person {
  // 생성자
  constructor(name) {
    this.name = name;
  }
  
  // 프로토타입 메서드
  sayHi() {
    console.log(`Hi! My name is ${this.name}`);
  }
  
  // 정적 메서드 (static)
  static sayHello() {
    console.log('Hello!');
  }
}

const me = new Person('Lee');
console.log(me.name);			// Lee

// 프로토타입 메서드 호출
me.sayHi();

// 정적 메서드 호출
Person.sayHello();

25.3 클래스 호이스팅

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

class Person {}

❗️ 클래스 선언문은 호이스팅이 발생하지 않는 것처럼 보이지만 그렇지 않다

const Person = '';
{
  console.log(Person);		// ReferenceError: Cannot access 'Person' before initialization
  class Person {}
}
  • 호이스팅이 발생하지 않는다면 ''이 출력되어야 하지만, ReferenceError 발생

  • 클래스 선언문도 호이스팅 발생
    let, const 키워드로 선언한 변수처럼 호이스팅 발생


25.4 인스턴스 생성

클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성

  • 일반 함수new 연산자 사용 여부에 따라 일반 함수로 호출되거나 인스턴스 생성을 위한 생성자 함수로 호출되지만,
    클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이므로 반드시 new 연산자와 함께 호출

  • 클래스 표현식으로 정의된 클래스 경우,
    기명 클래스 표현식의 클래스 이름을 사용해 인스턴스를 생성하면 에러 발생
    → 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능하기 때문❗️

class Person {}

const me = new Person();
console.log(me);
// ⚬ new 연산자 없이 클래스를 호출하면 타입 에러 발생
class Person {}

const me = Person();	// TypeError: Class constructor Person cannot be invoked without 'new'
// ⚬ 기명 클래스 표현식의 클래스 이름을 사용해 인스턴스를 생성하면 레퍼런스 에러 발생
const Person = class MyClass {} ;

const me = new Person();

console.log(MyClass);
const you = new MyClass();	// ReferenceError: MyClass is not defined

25.5 메서드

클래스는 몸체에는 0개 이상의 메서드만 선언할 수 있다

  • 클래스 몸체에서 정의할 수 있는 메서드
    • constructor(생성자), 프로토타입 메서드, 정적 메서드


1) constructor(생성자)

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

  • constructor는 이름 변경을 할 수 없음

  • constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킴

  • constructor는 클래스 내에 최대 1개만 존재할 수 있음

    • 2개 이상의 constructor 포함 시, SyntaxError 발생
  • constructor는 생략될 수 있음

    • 생략 시, 클래스에 다음과 같이 빈 constructor가 암묵적으로 정의
  • 프로퍼티가 추가되어 초기화된 인스턴스를 생성하려면 constructor 내부에서 this에 인스턴스 프로퍼티 추가

  • 클래스 외부에서 인스턴스 프로퍼티의 초기값을 전달하려면 constructor에 매개변수를 선언하고 인스턴스를 생성할 때 초기값을 전달

  • constructor는 별도의 반환문을 갖지 않아야 함

class Person {
  constructor(name) {
    // 인스턴스 생성 및 초기화
    this.name = name;
  }
}

const me = new Person('Lee');

2) 프로토타입 메서드

  • 클래스 몸체에서 정의한 메서드는 클래스에 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 됨

  • 클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 됨

class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHi() {
    console.log(`Hi! My name is ${this.name}`);
  }
}

const me = new Person('Lee');
me.sayHi();		// Hi! My name is Lee

3) 정적 메서드

  • 클래스에서 메서드에 static 키워드를 붙이면 정적 메서드 (클래스 메서드)가 됨

  • 정적 메서드는 클래스에 바인딩된 메서드가 됨

  • 정적 메서드는 프로토타입 메서드처럼 인스턴스로 호출하지 않고 클래스로 호출

    • 정적 메서드는 인스턴스로 호출할 수 없음
      → 정적 메서드가 바인딩된 클래스는 인스턴스의 프로토타입 체인 상 존재하지 않기 때문❗️
class Person {
  constructor(name) {
    this.name = name;
  }
  
  static sayHi() {
    console.log('Hi!');
  }
}

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

  • 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다름
  • 정적 메서드는 클래스로 호출 / 프로토타입 메서드는 인스턴스로 호출
  • 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만, 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있음

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

  • function 키워드를 생략한 메서드 축약 표현을 사용

  • 객체 리터럴과는 다르게 클래스에 메서드를 정의할 때에는 콤마가 필요 X

  • 암묵적으로 strict mode로 실행

  • for...in 문이나 Object.keys 메서드 등으로 열거할 수 없음

    • [[Enumerable]] 값이 false
  • 내부 메서드 [[Construct]]를 갖지 않는 non-constructor

    • new 연산자와 함께 호출할 수 없음

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

  • new 연산자와 함께 클래스를 호출하면 생성자 함수와 마찬가지로 클래스 내부 메서드 [[Construct]]가 호출된다

  • 다음과 같은 과정을 거쳐 인스턴스 생성

    인스턴스 생성과 this 바인딩 → 인스턴스 초기화 → 인스턴스 반환


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

  • new 연산자와 함께 클래스를 호출하면 constructor 내부 코드가 실행되기 앞서 암묵적으로 빈 객체 생성

  • 클래스가 생성한 인스턴스의 프로토타입으로 클래스의 prototype 프로퍼티가 가리키는 객체가 설정됨

  • 암죽적으로 생성된 빈 객체 (인스턴스)는 this에 바인딩

→ constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킴


2. 인스턴스 초기화

  • constructor 내부 코드가 실행되어 this에 바인딩되어 있는 인스턴스를 초기화

→ this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고, constructor가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티 값을 초기화
(만일 constructor가 생략되었다면 이 과정도 생략)


3. 인스턴스 반환

→ 클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환

class Person {
  // 생성자
  constructor(name) {
    // 1. 암묵적으로 인스턴스 생성되고 this에 바인딩
    console.log(this);
    console.log(Object.getPrototypeOf(this) === Person.prototype);	// true
    
    // 2. this에 바인딩되어 있는 인스턴스 초기화
    this.name = name;
    
    // 3. 완성된 인스턴스가 바인딩된 this를 암묵적으로 반환
  }
}

25.7 프로퍼티

인스턴스 프로퍼티

  • 인스턴스 프로퍼티는 constructor 내부에서 정의해야 함

접근자 프로퍼티

  • 접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
const person = {
  firstName: 'Ungmo',
  lastName: 'Lee',
  
  // fullName은 접근자 함수로 구성된 접근자 프로퍼티
  // getter 함수
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  
  // setter 함수
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
};

클래스 필드 정의 제안

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


private 필드 정의 제안

  • private 필드의 선두에는 #을 붙여준다

  • private 필드를 참조할 때도 #을 붙여주어야 한다

class Person = {
  // private 필드 정의
  #name = '';
  
  constructor(name) {
    // private 필드 참조
    this.#name = name;
  }
}

const me = new Person('Lee');
console.log(me.#name);		//SyntaxError: Private field '#name' must be declared in an enclosing class
  • public 필드는 어디서든 참조할 수 있지만 private 필드는 클래스 내부에서만 참조할 수 있음
접근 가능성publicprivate
클래스 내부OO
자식 클래스 내부OX
클래스 인스턴스를 통한 접근OX

static 필드 정의 제안

class MyMath {
  // static public 필드 정의
  static PI = 22 / 7;

  // static private 필드 정의
  static #num = 10;
  
  // static 메서드
  static increment() {
    return ++MyMath.#num;
  }
}

console.log(MaMath.PI);				// 3.1428..
console.log(MaMath.increment());	// 11

25.8 상속에 의한 클래스 확장

클래스 상속과 생성자 함수 상속

상속에 의한 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의하는 것


extends 키워드

상속을 통해 클래스를 확장하려면 extends 키워드를 사용하여 상속받을 클래스를 정의

class Base {}

class Dervied extends Base {}

동적 상속

  • extends 키워드는 클래스 뿐만 아니라 생성자 함수를 상속받아 클래스를 확장할 수도 있다

  • 하지만, extends 키워드 앞에는 반드시 클래스가 와야 함


서브클래스의 constructor


super 키워드

super 키워드는 함수처럼 호출할 수도 있고 this와 같이 식별자처럼 참조할 수 있는 특수한 키워드

  • super 키워드
    • super를 호출하면 수퍼클래스의 constructor를 호출
    • super를 참조하면 수퍼클래스의 메서드를 호출할 수 있음
profile
Hello zoni-World ! (◍ᐡ₃ᐡ◍)

0개의 댓글