25장. 클래스

유준상·2022년 2월 14일
1
  • 클래스는 프로토타입의 문법적 설탕인가?

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

    * ES6에 도입된 자바스크립트의 클래스는 사실 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 하는 문법적 설탕이다.

    클래스 vs 생성자 함수

    1) new 연산자
    클래스 : new 연산자 없이 호출하면 에러 발생
    생성자 함수 : new 연산자 없이 호출하면 일반 함수로서 호출
    2) 상속 키워드
    클래스 : extends, super 키워드 제공
    생성자 함수 : 없음
    3) 호이스팅
    클래스 : 호이스팅 없는 것처럼 동작
    생성자 함수 : 함수 및 변수 호이스팅 발생
    4) strict mode
    클래스 : 암묵적으로 strict mode 지정
    생성자 함수 : 지정 x
    5) 열거
    클래스 : 열거되지 않는다.
    생성자 함수 : 열거된다.

    --> 클래스는 새로운 객체 생성 메커니즘이다!!

  • 클래스 정의

    클래스 선언문

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

    --> 클래스는 표현식으로 정의 가능 ==> 클래스는 일급 객체이다.

    클래스 몸체에서 정의할 수 있는 메서드

    1) constructor (생성자)
    2) 프로토타입 메서드
    3) 정적 메서드

    // 클래스 선언문
    class Person {
       // constructor
       constructor(name) {
           this.name = name; // name 프로퍼티는 public
       }
       // 프로토타입 메서드
       sayHi() {
           console.log(`Hi! My name is ${this.name}`);
       }
       // 정적 메서드
       static sayHello() {
           console.log('Hello!');
       }
    }
    // 인스턴스 생성
    const me = new Person('Lee');
    // 인스턴스의 프로퍼티 참조
    console.log(me.name) // Lee
    // 프로토타입 메서드 호출
    me.sayHi();
    // 정적 메서드 호출
    Person.sayHello();

    함수 정의 방식 : 클래스 vs 생성자 함수

    1) 생성자
    클래스 : constructor()
    생성자 함수 : function 함수명()
    2) 프로토타입 메서드
    클래스 : 함수명()
    생성자 함수 : 생성자 함수명.prototype.함수명
    3) 정적 메서드
    클래스 : static 함수명()
    생성자 함수 : 생성자 함수명.함수명

  • 클래스 호이스팅

    --> 클래스는 함수로 평가 ==> 런타임 이전에 먼저 평가되어 함수 객체 생성
    클래스는 클래스 정의 이전에 참조할 수 없다.
    --> 클래스 선언문도 호이스팅이 발생하지만, let, const 키워드로 선언한 변수처럼 호이스팅된다. ==> 일시적 사각지대에 빠진다.

  • 인스턴스 생성

    클래스 ==> 생성자 함수, new 연산자와 함께 호출되어 인스턴스 생성

    class Person {}
    // 인스턴스 생성
    const me = new Person();
    console.log(me); // Person {}

    --> 클래스는 new 연산자와 함께 호출하지 않으면 에러가 발생

    * 기명 함수 표현식과 마찬가지로 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능 ==> 식별자 이름으로 접근한다.

  • 메서드

    1. constructor (생성자)

    --> 인스턴스를 생성하고 초기화하기 위한 특수한 메서드 -> 이름 변경 불가능

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

    --> constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킨다.

    생성자 함수 vs constructor

    1) constructor는 클래스 내에 최대 1개만 존재 가능!!
    --> 생략 시 암묵적으로 빈 constructor 정의
     constructor에 매개변수를 선언하면 클래스 외부에서 인스턴스 프로퍼티의 초기값 전달이 가능하다.

    class Person {
       constructor(name, address) {
           // 인수로 인스턴스 초기화
           this.name = name;
           this.address = address;
       }
    }
    // 인수로 초기값 전달 --> constructor로
    const me = new Person('Lee', 'Seoul') // Lee, Seoul이 consturctor로 전달

    --> constructor에는 return문을 생략한다. -> 의도치않은 반환 발생

    2. 프로토타입 메서드

    생성자 함수 : 명시적으로 prototype에 메서드 추가
    클래스 : 내부에서 정의한 메서드는 기본적으로 프로토타입 메서드로 작동
    클래스도 역시 프로토타입 체인을 생성한다!!

    3. 정적 메서드

    --> 인스턴스를 생성하지 않아도 호출할 수 있는 메서드
    생성자 함수 : 생성자 함수명의 메서드로 호출
    클래스 : static 키워드 사용
    정적 메서드는 프로토타입이 아닌 클래스에 바인딩된 메서드이다!!
    또한 정적 메서드는 인스턴스로 호출이 불가능하다!! --> 체인상에 존재 x

    4. 정적 메서드 vs 프로토타입 메서드

    1) 이 둘은 속해 있는 프로토타입 체인이 다르다.
    2) 정적 메서드는 클래스로 호출, 프로토타입 메서드는 인스턴스로 호출한다.
    3) 정적 메서드는 인스턴스 프로퍼티 참조 불가능, 프로토타입 메서드는 가능


    1번. 정적 메서드 --> 인스턴스 프로퍼티 참조 x

    class Square {
       static area(width, height) {
           return width * height;
       }
    }
    console.log(Square.area(10, 10)); // 100

    2번. 프로토타입 메서드 --> 인스턴스 프로퍼티 참조

    class Square {
       constructor(width, height) {
           this.width = width;
           this.height = height;
       }
       area() {
           return this.width * this.height;
       }
    }
    const square = new Square(10, 10);
    console.log(square.area()); // 100

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

    1) function 키워드 생략한 메서드 축약 표현 사용
    2) 객체 리터럴과 다르게 콤마 사용 x
    3) strict mode 암묵적 실행
    4) 열거 불가능
    5) 내부 메서드는 non-constructor --> new 연산자와 호출 불가능

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

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

    클래스 호출 시 암묵적으로 빈 객체 생성 (클래스가 생성한 인스턴스) -> 빈 객체는 this에 바인딩

    2. 인스턴스 초기화

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

    3. 인스턴스 반환

    바인딩된 this가 암묵적으로 반환

    과정

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

    1. 인스턴스 프로퍼티

    --> 인스턴스 프로퍼티는 constructor 내부에서 정의

    class Person {
       constructor(name) {
           // 인스턴스 프로퍼티
           this.name = name;
       }
    }
    const me = new Person('Lee');
    console.log(me); // Person {name: 'Lee'}

    --> constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 된다.

    2. 접근자 프로퍼티

    --> 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티

    class Person {
       constructor(firstName, lastName) {
           this.firstName = firstName;
           this.lastName = lastName;
       }
       // 접근자 프로퍼티
       get fullName() {
           return `${this.firstName} ${this.lastName}`;
       }
       // 접근자 프로퍼티 setter 함수
       set fullName(name) {
           [this.firstName, this.lastName] = name.split(' ');
       }
    }
    const me = new Person('Ungmo', 'Lee');

    * getter, setter 이름은 인스턴스 프로퍼티처럼 사용된다. (참조)
    1) getter는 취득할 때 사용하므로 반드시 무언가를 반환해야한다.
    2) setter는 무언가를 할당할 때 사용하므로 반드시 매개변수가 필요 (1개만 가능)
    --> 접근자 프로퍼티 또한 인스턴스 프로퍼티가 아닌 프로토타입 프로퍼티다.

    3. 클래스 필드 정의 제안

    클래스 필드 : 클래스가 생성할 인스턴스의 프로퍼티
    * 자바스크립트의 클래스 몸체에서는 메서드만 선언이 가능하다.


    자바스크립트에서도 클래스 기반 객체지향 언어와 같이 클래스 몸체 내부에서 인스턴스 프로퍼티를 선언할 수 있다. (단, this에 바인딩하면 안 된다.)
    --> this는 constructor와 메서드 내부에서만 유효하다.

    4. private 필드 정의 제안

    자바스크립트는 캡슐화를 완전하게 지원하지 않는다.
    --> 언제나 public 이다. (constructor, 클래스 필드 정의 제안 모두)


    새롭게 private 기능 제안 --> #을 추가한다.

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

    public vs private

    public : 어디서나 참조 가능
    private : 클래스 내부에서만 참조 가능
    --> 클래스 외부에서 private 필드에 간접적으로만 접근이 가능하다.
    --> 접근자 프로퍼티 get으로 가능!!

    private 필드는 constructor가 아닌 클래스 몸체에서 정의!!

    5. static 필드 정의 제안

    static public, static private 필드 정의 제안

    class MyMath {
       // static public 필드
       static PI = 22 / 7;
       // static private 필드
       static #num = 10;
       // static 메서드
       static increment() {
           return ++Mymath.#num;
       }
    }
  • 상속에 의한 클래스 확장

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

    프로토타입 기반 상속과 상속에 의한 클래스 확장은 다르다.
    --> 상속에 의한 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장
    --> 상속받은 클래스로부터의 속성 + 확장 속성 (코드의 재사용)

    class Animal {
       constructor(age, weight) {
           this.age = age;
           this.weight = weight;
       }
       eat() { return 'eat'; } // 프로토타입 메서드 eat
       move() { return 'move'; } // 프로토타입 메서드 move
    }
    // 상속을 통해 Animal 클래스를 확장한 Bird 클래스
    class Bird extends Animal {
       fly() { return 'fly'; }
    }
    const bird = new Bird(1,5);

    --> extends 키워드로 상속 구현 가능

    2. extends 키워드

    // 수퍼 클래스
    class Base {}
    // 서브 클래스
    class Derived extends Base {}

    --> 수퍼, 서브클래스는 인스턴스, 클래스 프로토타입 체인을 모두 생성한다.
    --> 프로토타입, 정적 메서드 모두 상속이 가능하다.

    3. 동적 상속

    --> extends 키워드는 클래스뿐 아니라 생성자 함수를 상속받아 클래스 확장 가능

    // 생성자 함수
    function Base(a) {
       this.a = a;
    }
    // 생성자 함수를 상속받는 클래스
    class Derived extends Base {}
    const derived = new Derived(1);
    console.log(derived); // Derived {a : 1}

    --> extends 키워드는 함수 객체로 평가될 수 있는 모든 표현식을 상속받을 수 있다. -> 동적 상속 가능

    4. 서브클래스의 constructor

    --> 수퍼, 서브클래스 모두 constructor를 생략하면 빈 객체가 생성
    --> 인스턴스 프로퍼티를 소유하기 위해서는 constructor 내부에 추가

    5. super 키워드

    --> 호출과 참조가 모두 가능한 특수 키워드
    1) 호출 : 수퍼클래스의 constructor 호출
    2) 참조 : 수퍼클래스의 메서드 호출

    1. 호출

    수퍼클래스에 constructor를 생략하지 않고, 서브클래스에 constructor를 생략하면 암묵적으로 수퍼클래스의 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); // Derived {a:1, b:2, c:3}

    * super 호출 시 주의사항
    1) 서브클래스에서 constructor 생략하지 않는 경우 반드시 super 호출
    2) 서브클래스의 constructor에서 super 호출 전까지 this 참조 불가능
    3) super는 서브클래스의 constructor에서만 호출가능

    2. 참조

    --> 수퍼클래스의 메서드 호출 가능

    // 수퍼클래스
    class Base {
       constructor(name) {
           this.name = name;
       }
       sayHi() {
           return `Hi! ${this.name}`;
       }
    }
    // 서브클래스
    class Derived extends Base {
       sayHi() {
           return `${super.sayHi()}. how are you doing?`;
       }
    }
    const derived = new Derived('Lee');
    console.log(derived.sayHi()); // Hi! Lee. how are you doing?

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

    // 수퍼클래스
    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() {
           return super.toString() + `, color = ${this.color}`;
       }
    }

    1. 서브클래스의 super 호출

    서브클래스는 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임 --> 서브클래스에서 반드시 super 호출이 필요
    --> 서브클래스 constructor에 super가 없으면 인스턴스 생성이 불가능하므로 에러 발생

    2. 수퍼클래스의 인스턴스 생성과 this 바인딩

    수퍼클래스는 빈 객체 생성 -> 수퍼클래스 constructor 내부의 this는 생성된 인스턴스를 가리킨다.
    !! 서브 클래스의 인스턴스는 서브클래스가 생성한 것으로 처리 !!
    --> 생성된 인스턴스의 프로토타입은 서브클래스의 prototype 프로퍼티가 가리키는 객체

    3. 수퍼클래스의 인스턴스 초기화

    --> 수퍼클래스의 constructor 실행, this에 바인딩되어 있는 인스턴스 초기화

    4. 서브클래스 constructor로의 복귀와 this 바인딩

    --> 서브클래스는 별도의 인스턴스 생성 x, super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용

    5. 서브클래스의 인스턴스 초기화

    6. 인스턴스 반환

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

    --> extends 키워드는 클래스뿐 아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식에 사용 가능

profile
웹사이트 개발과 신발을 좋아하는 학생입니다.

0개의 댓글

관련 채용 정보