[modern JS Deep Dive] - 25장 . 클래스

유선향·2025년 1월 18일
0

<modern_JS_Deep_Dive>

목록 보기
25/44

문법적 설탕 인가?

자바스크립트는 클래스가 필요없는 프로토타입 기반 객체지향 언어다

클래스 기반 언어에 익숙한 프로그래머를 위해 es6에서 클래스가 클래스 기반 객체지향 프로그래밍 언어와 흡사한 새로운 객체 생성 매커니즘을 제시

//생성자 함수
var Test = (function () {
    function Test(x) {
        this.x = x;
        this.Hi = function () {
            console.log('Hi' + this.x);
        }
    }
    return Test;
})();
	
	var test1 = new Test("생성자")
	
//클래스
class Test2 {
	constructor(x){this.x=x}
	Hi(){console.log('Hi'+this.x)}
	}
	const test2 = new Test2('클래스')

	console.dir(test1)
	console.dir(test2)
	
	test1.Hi()
	test2.Hi()

클래스와 생성자 함수 차이

구분클래스생성자 함수
new없이 호출시 에러없이 호출하면 일반함수로서 호출됨
extends / super제공 (상속관계 구현을 더욱 간결하고 명료)지원 x
호이스팅발생하지 않는것처럼 동작
(함수와 다르게 TDZ(Temporal Dead Zone, 일시적 사각지대)가 존재하기 때문에 사용하기 전에 반드시 선언)함수 선언문으로 정의된 생성자함수 → 함수 호이스팅
함수 표현식 정의 → 변수 호이스팅
strict mode암묵적으로 지정되어 실행, 해제가능암묵적으로 지정되지 않음
constructor, 프로토타입 ,정적 메소드모두 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false임 즉, 열거 되지 않음존재

결론: 클래스를 흔히 프로토타입 기반 객체 생성 패턴의 단순한 문법적 설탕이라고 하는데, 저자는 새로운 객체 생성 메커니즘으로 본다.


클래스 정의

class 키워드를 사용하여 정의

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

//기명 클래스 표현식
const Test = class MyClass {};

클래스는 일급 객체 (=즉 함수로 평가됨)

  • 변수에 할당할 수 있다.
  • 함수의 인자로 전달할 수 있다.
  • 함수의 반환값으로 사용할 수 있다.
  • 동적으로 클래스 자체에 프로퍼티를 추가하거나 수정할 수 있다.
  • 무명의 리터럴로 생성할수 있다 ⇒ 런타임에서 생성 가능
  • 변수나 자료구조(객체, 배열등) 에 저장할 수 있다.

결론: 클래스는 일급객체여서 위의 특징이 활용됨.

클래스 호이스팅

결론: 클래스 선언문 이전에 일시적 사각지대(TDZ)에 빠지기 때문에 호이스팅이 발생하지 않는것처럼 동작한다.

따라서 선언전에 호출 x

인스턴스 생성

결론: 클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이므로 반드시 new 연산자와 함께 호출해야한다.

→ 에러발생

메서드

결론: 클래스 몸체 ({중괄호 안}) 에는 constructor, 프로토타입 메서드, 정적 메서드 등 세가지의 메서드만 선언할 수 있다. ⇒ 다른건 문법 에러남 최신문법에서는 필드, 인스턴스 프로퍼티 선언 가능

constructor

constructor는 인스턴스를 생성하고 초기화하기위한 특수한 메서드, 이름 변경 x

결론: constructor

  1. 클래스 내에 최대 한개만 존재 가능.
  2. 클래스의 constructor new 연산자와 함께 호출될 때 자동으로 인스턴스를 반환 ∴return 을 사용 x
  3. 클래스의 constructor 메서드는 단순한 메서드가 아님. ⇒ 인스턴스 생성를 생성하는 함수의 일부로 동작
  4. 클래스도 함수 객체 고유의 프로퍼티를 모두 갖고있고, 자신의 스코프 체인 구성
  5. constructor 생략 시 자바스크립트는 자동으로 인스턴스를 생성하는 동작만 수행하는 기본 생성자를 추가함. 추가적인 초기화 작업이 필요하면 constructor를 직접 작성해야 합니다.
class Test1 {
    this.x= x; //문법 오류
}

class Test2 {
    constructor(x) {
        this.x = x; 
    }
}

그래서 사실 class 의 몸통은 두부분으로 나뉨 => 최신문법으로는 세부분
1.constructor: 인스턴스를 생성할 때 필요한 초기화 작업을 처리.
2. 메서드: 인스턴스가 수행할 수 있는 행동들을 정의.
3. 인스턴스 프로퍼티 (클래스 필드): 
	클래스 내부에서 this 없이 정의한 프로퍼티. 인스턴스가 생성될 때 자동으로 해당 프로퍼티가 추가

class Test2 {
    constructor(x) {
        this.x = x; 
    }
    hi() {
       console.log(this.x)
    }
}

//만약 위에서 constructor가 없으면?
class Test2 {
    // constructor가 없으므로 기본 생성자가 추가
    hi() {
        console.log(this.x); // this.x는 초기화되지 않아서 undefined일 수 있음
    }
}

메서드

결론: 프로토타입 메서드와 정적 메서드는 클래스 내에서 사용하는 방식과 목적에 따라 차이남

프로토타입 메서드

프로토타입 메서드는 인스턴스를 통해 호출되는 메서드

  • 생성자 함수를 사용하여 인스턴스를 생성하는 경우 프로토타입 메서드를 생성하기 위해서는 다음과 같이 명시적으로 프로타입에 메서드를 추가해야한다.

정적 메서드

정적메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드

  • 생성자 함수의 경우 정적 메서드를 생성하기 위해서는 이전에 본것 처럼 생성자 함수에 메서드를 추가해야 함.
  • 클래스에서는 메서드에 static 키워드를 붙이면 정적 메서드(클래스 메서드)가 된다.

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

  1. 속해 있는 프로토타입 체인이 다름.
    • 정적 메서드는 클래스 자체에 속하고, 프로토타입 체인에 포함되지 않음
    • 프로토타입 메서드는 인스턴스의 프로토타입 체인에 포함되어 인스턴스를 통해 호출할 수 있음.
  2. 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출
  3. 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.
    • 정적 메서드는 this가 클래스 자체를 가리킴 ∴ 인스턴스의 프로퍼티에 접근할 수 없음.
    • 프로토타입 메서드는 this가 인스턴스를 가리킴. 인스턴스의 프로퍼티를 참조할 수 있음.

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

  1. function 키워드를 생략한 메서드 축약 표현을 사용한다
  2. 객체 리터럴과는 다르게 클래스에 메서드를 정의할 때는 콤마가 필요없다.
  3. 암묵적으로 strict mode로 실행된다.
  4. for … in 문이나 Object.keys 메서드 등으로 열거할 수 없다. 즉 프로티의 열거 가능 여부를 나타내며, 불리언 값을 갖는 프로퍼티 어트리뷰트 [[Enumerable]] 값이 false 다.
  5. 내부 메서드 [[construct]]를 갖지 않는 non-constructor다. 따라서 new 연산자와 함께 호출 할 수 없다.

인스턴스

인스턴스 생성과 this 바인딩

  • new 연산자와 클래스 호출:
    • 클래스를 new 연산자와 함께 호출하면, 빈 객체가 암묵적으로 생성
    • 이 빈 객체는 this에 바인딩되고, constructor 내부에서 사용되는 this는 바로 이 빈 객체를 가리킴
  • 프로토타입 설정:
    • 생성된 빈 객체는 constructor의 prototype 속성이 가리키는 객체를 프로토타입으로 설정⇒ 이로 인해 생성된 객체는 클래스의 메서드를 사용가능

인스턴스 초기화

  • constructor가 실행되면, this에 바인딩된 빈 객체에 프로퍼티가 추가되고, constructor에서 전달된 초기값으로 객체 속성들이 초기화

인스턴스 반환

  • constructor 내부 코드가 실행을 마친 후, this는 완성된 인스턴스를 가리키고, 이 인스턴스는 암묵적으로 반환 (아까 그래서 return 하면 안된다 했음)
  • 사실, constructor는 this를 명시적으로 반환하지 않아도 자동으로 this를 반환하는 특성 가지고있음

프로퍼티

인스턴스 프로퍼티

  • 인스턴스 프로퍼티는 constructor 내부에서 정의해야 한다.
  • 내부 코드가 실행되기 이전에 constructor 내부의 this에는 이미 클래스가 암묵적으로 생성한 인스턴스인 빈 객체가 바인딩 되어있다.
  • this 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 된다. 인스턴스 프로퍼티는 언제나 public 하다.
  • 클래스에서 인스턴스 프로퍼티를 private 하게 정의하는 방법은 ES2022(ECMAScript 2022)에서 도입된 private 필드를 사용하여 가능

접근자 프로퍼티

  • 자체적으로는 값을 갖지 않고 다른 접근자 함수로 구성된 프로퍼티

결론: 인스턴스 프로퍼티는 constructor 내부에서 정의(public or private)

클래스 필드 정의 제안

결론: 클래스 필드로 인해 자바스크립트도 자바의 클래스 정의방식 처럼 필드 를 사용할수 있게 됨.

자바스크립트는 기존에 클래스 몸체에 메서드만 선언할 수 있었으나, 최신 사양에서는 클래스 필드를 선언하고 private 필드와 static 필드를 사용할 수 있게 됨.

클래스 필드

자바의 클래스 필드처럼 인스턴스 프로퍼티를 초기화 없이 선언할 수 있게 클래스 몸체에 직접 필드를 정의

class Test {
    // 클래스 필드 정의 (인스턴스 프로퍼티)
    name = '유선향'; 

    constructor(age) {
        this.age = age;
    }

    greet() {
        console.log(`Hello, ${this.name}`);
    }
}

private 필드

  • 자바스크립트는 캡슐화를 완전하게 지원하지 않지만, private 필드를 정의할 수있는 새로운 표준 사양이 제안 되어있음.
  • private 필드는 클래스 내부에서만 참조할수있고, 외부에서 접근 x(접근자프로퍼티로 간접 접근 유효)
  • private 필드는 반드시 클래스 몸체에 정의해야 한다. private 필드를 직접 constructor 에 정의하면 에러가 발생한다.
class Counter {
    // private static 필드 정의
    static #count = 0;

    constructor() {
        Counter.#count++;
    }

    static getCount() {
        return Counter.#count;
    }
}

const counter1 = new Counter();
const counter2 = new Counter();
console.log(Counter.getCount());  // 2
console.log(Counter.#count);      // SyntaxError: Private field '#count' must be declared in an enclosing class

static 필드 정의제안

  • 원래는 static 키워드를 사용해 정적 필드를 정의할 수는 없었는데, 최근 제안된 사양으로는 static 필드와 private static 필드도 정의할 수 있게 됨.
class Counter {
    // static 필드 정의 => 클래스의 모든 인스턴스가 공유
    static count = 0;

    constructor() {
        Counter.count++;
    }

    static getCount() { // static 메서드 => 인스턴스 생성안해도 호출 가능 
        return Counter.count;
    }
}

const counter1 = new Counter();
const counter2 = new Counter();
console.log(Counter.getCount());  // 2

상속에 의한 클래스 확장

결론: 클래스 상속이랑 생성자 함수 상속은 다름

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

  • 프로토타입 기반 상속은 프로토타입 체인을 통해 다른 객체의 자산을 상속받는 개념
  • 상속에 의한 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장 하여 정의하는 것
  • 생성자 함수는 클래스와 같이 상속을통해 다른 생성자 함수를 확장할수 없음

extends 키워드 (클래스 상속)

  • extends 키워드를 사용하여 상속받을 클래스를 정의
  • extends 는 슈퍼클래스와 서브클래스 간의 상속관계를 설정한다. 클래스도 프로토타입을 통해 상속관계를 구현한다.

상속을 통해 확장된 클래스 : 서브 클래스 , 파생 클래스, 자식 클래스

서브 클래스에게 상속된 클래스: 수퍼클래스, 베이스 클래스, 부모클래스

class Parent {
    constructor(x) {
        this.x = x;
    }
    hi() {
        console.log(`잘잤니?`);
    }
}

class Child extends Parent{
    constructor(x, y) {
        super(x);  
        this.y = y;
    }
    hi() {
		    super.hi(); //부모 클래스 메서드 호출
        console.log(`안녕히 주무셨어요?`);
    }
}

// 자식 클래스의 인스턴스 생성
const childInstance = new Child('x','y');
childInstance.hi(); //잘잤니? 안녕히 주무셨어요?

서브클래스의 constructor

class Child extends  Parent{
    constructor(x, y) {
        super(x);  // 반드시 super()로 부모 클래스의 constructor 호출
        this.y = y;
    }

수퍼클래스와 서브클래스 모두 constructor 를 생략하면 빈객체가 생성된다

super 를 호출할때 주의점

  1. 서브 클래스에서 constructor를 생략하지 않는경우 서브클래스의 constructor에서 반드시 super를 호출
  2. 서브클래스의 constructor에서 super를 호출하기전에는 this를 참조할 수 없다.
  3. super는 반드시 서브클래스의 constructor 에서만 호출한다 다른 클래스에서 super 호출시 에러

동적상속

  • extends 키워드는 생성자 함수를 상속받아 클래스를 확장할 수 도있다. 단 , extends 키워드 앞에는 반드시 클래스가 와야 함.
  • extends 키워드 다음에는 [[constructor]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다. 이를 통해 동적으로 상속받을 대상을 결정할 수 있다.

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

상속을 통한 클래스 인스턴스 생성 과정은 서브클래스와 수퍼클래스가 협력

서브클래스의 constructor가 super()를 호출함으로써 부모 클래스의 인스턴스를 생성하고,

이후 서브클래스에서 추가적인 초기화 작업을 수행

표준 빌트인 생성자 함수 확장

내부 메서드를 갖는 함수 객체로 평가 될 수 있는 모든 표현식을 사용할 수 있다.

자바스크립트의 빌트인 객체들(Array, String, Date 등)도 생성자 함수이기 때문에, extends를 사용하여 확장 가능

class MyArray extends Array {
    uniq() {
        return [...new Set(this)];
    }
}

const arr = new MyArray(1, 2, 3, 3, 4);
console.log(arr.uniq());  // [1, 2, 3, 4]

0개의 댓글