[JS 개념정리] 클래스_class

rlorxl·2022년 2월 18일
0

Javascript

목록 보기
8/12

Class

Class는 객체를 생성하기 위한 템플릿이라고 보면 된다.
속성(fields(data))과 행동(methods)을 포함한다.
인자는 생성자(constructor: 초기값을 객체 안으로 넣어 필요한 데이터를 전달한다)로 받는다.

  • class A{}와 같은 선언적 방식으로 선언하거나
  • const B = class{}와 같이 class표현식을 변수에 할당하는 방식으로 사용할 수 있고
  • new생성자를 사용해서 인스턴스를 생성할 수 있다.

기존에 생성자 함수를 이용해서 객체를 생성하면 이런 형식이 될 것이다.

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getName = function(){
    return this.name + '입니다';
}

이걸 똑같이 클래스로 만들면 메서드를 프로토타입으로 빼지 않고도 클래스 내부에 지정해줄 수 있다.

클래스 몸체에서 정의한 메서드는 prototype프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다. 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다.

class Person{
    constructor(name,age,location){
      //fields
        this.name = name;
        this.age = age;
        this.location = location;
    }
	//prototype methods
    getName(){
        return this.name + '입니다';
    }
}

new생성자로 인스턴스 객체를 생성하고 출력했을 때 결과이다.

const me = new Person('jang',10,'Korea');
const you = new Person('kim',20,'China');

console.log(me); //Person { name: 'jang', age: 10, location: 'Korea' }
console.log(you); //Person { name: 'kim', age: 20, location: 'China' }
console.log(me.getName()); //jang입니다

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

  • 클래스를 new연산자 없이 호출하면 에러가 발생하지만 생성자 함수를 new연산자 없이 호출하면 일반함수로서 호출된다.

  • 클래스는 상속을 지원하는 extendssuper 키워드를 제공한다.

  • 클래스는 호이스팅이 발생하지 않는것처럼 동작한다.

  • 클래스내의 모든 코드는 암묵적으로 strict mode가 지정되어 실행되며 이를 해제할 수 없다. (메서드 내의 this가 전역이 아니라 undefined가 뜨는 이유
    해결 → 화살표 함수 사용하기. (화살표 함수는 함수자체의 this바인딩을 갖지 않기 때문에))

  • 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]] 값이 false이다. (열거불가)


Class 특징

  • 일반적으로 파스칼케이스를 사용하지만 파스칼케이스를 사용하지 않아도 에러가 발생하진 않는다.

  • 표현식으로 정의할 수도 있다. 이때는 클래스도 함수와 마찬가지로 익명, 기명 모두 가능하다.

  • 클래스를 표현식으로 정의할 수 있다는 것은 클래스가 값으로 사용할 수 있는 일급 객체라는 것을 의미한다. (클래스는 함수로 평가된다. = 일급객체)

클래스가 함수로 평가된다는 것은 함수 객체의 고유 프로퍼티를 갖고 있다는것을 의미한다. 프로토타입이 연결되어 있으며 자신의 스코프체인을 구성한다.

  • 클래스 내부 메서드는 [[Construct]]를 갖지 않는 non-contructor다.

  • 클래스 몸체에서 정의할 수 있는 메서드는 constructor, 프로토타입 메서드, 정적 메서드가 있다.

💡 정적 메서드

  • 메서드앞에 static을 붙이면 정적 메서드가 된다. (클래스에 바인딩된 메서드)
    ⇒ 클래스는 함수객체로 평가되므로 자신의 프로퍼티/메서드를 소유할 수 있다.
  • 정적메서드는 클래스 정의 이후 인스턴스를 생성하지 않아도 호출할 수 있다. (인스턴스로 호출하지 않고 클래스로 호출한다. (Person.sayhello())
    ⇒ 정적 메서드가 바인딩된 클래스는 인스턴스의 프로토타입 체인상에 존재하지 않는다.) + 정적 메서드의 this 바인딩은 클래스를 가리킨다.

constructor

constructor내부에서 this에 추가한 프로퍼티는 인스턴스 프로퍼티가 된다. constructor 내부의 this는 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스를 가리킨다.
constructor는 생략할 수 있다. 생략하면 클래스에 빈 constructor가 암묵적으로 정의되고 이것에 의해 빈 객체를 생성한다.

class Base {
	// constructor() {} -> 암묵적으로 정의된다.
}

class Derived extends Base {
	// constructor(...args) { super(...args) } -> 암묵적으로 정의된다.
}

const derived = new Derived();
console.log(derived) // Derived {}

constructor는 별도의 반환문을 갖지 않아야 한다. new연산자와 함께 클래스가 호출되면 암묵적으로 this를 반환하기 때문이다. (생성자 함수와 동일)


상속에 의한 클래스 확장

하나의 클래스에서 만든 데이터를 다른 클래스에서도 상속받아 사용할 수 있다.

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

클래스도 프로토타입을 통해 상속 관계를 구현한다.

수퍼클래스와 서브클래스는 인스턴스의 프로토타입 체인뿐 아니라 클래스 간의 프로토타입 체인도 생성한다. 이를 통해 프로토타입 메서드, 정적 메서드 모두 상속이 가능하다.→ 코드 재사용 관점에서 유용하다.

상속 (extends)

상속을 통해 클래스를 확장하려면 extends 키워드를 사용하여 상속받을 클래스를 정의한다.
extends 키워드의 역할은 수퍼클래스와 서브클래스간의 상속관계를 설정하는 것이다.
Child.prototype.__proto__가 Parent.prototype이 되므로 메서드 전체가 상속된다.

class Animal{
    constructor(name, sound){
        this.name = name;
        this.sound = sound;
    }

    getInfo(){
        console.log(
            this.name + '가(이)' + this.sound + '소리를 낸다.'
        );
    }
}
// 확장
class Friends extends Animal{
    constructor(name, sound){ 
        super(name, sound)
    }
}

const dog = new Friends('개','멍멍');
const cat = new Friends('고양이','냐옹');

dog.getInfo(); // 개가(이)멍멍소리를 낸다.
cat.getInfo(); // 고양이가(이)냐옹소리를 낸다.

클래스 Friends를 사용해서 dog,cat이라는 인스턴스를 만들었고 이 인스턴스들은 Friends의 부모인 Animal이 가지고 있는 메서드에 접근할 수 있다.

💡 여기서는 Friends에 기존 클래스(부모)와 똑같이 생성자(constructor)를 만들었지만 생성자를 만들지 않아도 자동적으로 부모의 constructor를 호출하여 만들어진다.

또한 상속받은 클래스에 커스텀 생성자를 추가하고, 기존 부모 생성자도 상속받아 사용하고 싶은 경우 위와 같이 super라는 키워드를 활용하면 부모 생성자를 그대로 쓰는것이 가능하다.

오버라이딩 (overiding)

상속받은 클래스안에서는 새로운 메서드를 추가로 정의할 수도 있고 기존 부모가 가지고 있던 메서드를 오버라이딩(overiding: 덮어쓰기)해서 재정의하여 사용할 수 있다.
부모의 메서드, 생성자를 호출할때는 super.method(), super(constructor)를 사용한다.

class Friends extends Animal{
    constructor(name, sound){ 
        super(name, sound) // 부모의 생성자 함수를 호출
    }
    sleep() {
        console.log(this.name + '가(이) 자고 있다.');
    }
    getInfo(){ // 오버라이딩할 메서드
            super.getInfo(); 
            this.sleep();
    }
}

const dog = new Friends('개','멍멍');
const cat = new Friends('고양이','냐옹');

dog.sleep(); // 개가(이) 자고 있다.
cat.getInfo(); // 고양이가(이)냐옹소리를 낸다. 고양이가(이) 자고 있다.

super

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

1. super을 호출하면 수퍼클래스의 constructor를 호출한다.
2. super를 참조하면 수퍼클래스의 메소드를 호출할 수 있다.

super을 호출하면 수퍼클래스의 constructor를 호출한다.

Q. 수퍼클래스의 constructor에서 추가한 프로퍼티를 그대로 갖는 인스턴스만 생성한다면?

서브클래스의 constructor를 생략할 수 있다. 이 때 인스턴스를 만들면서 전달한 인수는 모두 서브클래스에 암묵적으로 정의된 constructor의 super 호출을 통해 수퍼 클래스의 constructor에 전달된다.

Q. 수퍼클래스에서 추가한 프로퍼티, 서브클래스에서 추가한 프로퍼티를 둘다 갖는 인스턴스를 생성한다면?

서브클래스의 constructor를 생략할 수 없다. 이 때 수퍼클래스의 constructor에 전달할 필요가 있는 인수는 서브클래스의 constructor에서 호출하는 super를 통해 전달한다.
(상속관계의 두 클래스가 서로 협력하여 인스턴스를 생성한다.)

📍super 호출 시 주의사항

  • 서브클래스에서 constructor를 생략하지 않는 경우 서브클래스의 constructor에서는 반드시 super를 호출해야 한다.
  • 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없다.
  • super는 반드시 서브클래스의 constructor안에서만 호출한다.

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

(1) 서브클래스의 super 호출

서브클래스가 new연산자와 함께 호출되면 서브클래스 constructor내부의 super키워드가 함수처럼 호출된다.

서브클래스의 생성자 함수가 실행되면 [[constructorKind]](자바스크립트 엔진의 내부 슬롯) 값이 ‘derived’이기 때문에 일반 클래스에서 일어나는 빈 객체를 생성하고 this에 바인딩하고~ 하는 일들이 일어나지 않는다.

  • [[constructorKind]]값이 ‘base’ - 다른 클래스를 상속받지 않는 클래스
  • [[constructorKind]]값이 ‘derived’ - 다른 클래스를 상속받는 서브클래스

→ 그래서 서브클래스는 수퍼클래스에게 인스턴스 생성을 위임한다.
→ constructor에서 super호출이 필수

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

수퍼클래스의 constructor 내부의 코드가 실행되기 이전에 암묵적으로 빈 객체를 생성한다. 그리고 그 객체는 this에 바인딩 된다. (this는 생성된 인스턴스를 가리킨다.) 이 때 인스턴스는 수퍼클래스가 생성한 것이다.

class Rectangle {
	constructor(width, height) {
		console.log(this) // ColorRectangle {} - 수퍼클래스 안에서 this를 찍어도 서브클래스의 인스턴스가 나온다. 호출된 클래스가 서브클래스이기 때문에 
		console.log(new.target) // ColorRectangle
	}
}

이 때 호출된 함수를 가리키는 new.target은 서브클래스를 가리킨다. 인스턴스는 서브클래스가 생성한 것으로 처리된다. 따라서 생성된 인스턴스의 프로토타입은 서브클래스의 prototype프로퍼티가 가리키는 객체다. → Object.getPrototypeOf(this) === ColorRectangle.prototype // true

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

수퍼클래스의 constructor가 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다. = this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티를 초기화한다.

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

super호출이 종료되고 제어 흐름이 서브클래스 constructor로 돌아온다. 이때 super가 반환한 인스턴스가 this에 바인딩된다. ( 서브클래스 내부에서 this를 찍으면 이제 ColorRectangle { width: 2, height: 4 }가 나온다.
→ 서브클래스의 constructor에서 super를 호출하기 전에 this를 참조할 수 없는 이유

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

서브클래스의 constructor에 기술되어있는 인스턴스 초기화가 실행된다. this에 바인딩 되어있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티를 초기화한다.

(6) 인스턴스 반환

완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.


super를 참조하면 수퍼클래스의 메소드를 호출할 수 있다.

서브클래스의 프로토타입 메서드 내에서 super.toString은 수퍼클래스의 프로토타입 메서드 toString를 가리킨다.

💡 super 참조가 동작하기 위해서는 super를 참조하고 있는 메서드 (서브클래스 메서드)가 바인딩되어 있는 객체 (서브클래스.prototype)의 프로토타입 (수퍼클래스의 prototype )를 찾을 수 있어야한다. ⇒ ’super.toString’는 수퍼클래스의 prototype프로퍼티에 바인딩된 프로토타입을 참조할 수 있어야 한다.

0개의 댓글