HyunJun 기술 블로그

클래스 (Class), Prototype based, Class based 본문

JavaScript

클래스 (Class), Prototype based, Class based

공부 좋아 2023. 7. 12. 09:20
728x90
반응형

1. Prototype based VS Class based

기본적으로 자바스크립트는 프로토타입 기반으로 시작했고, 프로토타입 기반의 언어이다. Class는 ES6에서 나온 기능이기 때문에, 클래스도 내부적으로 조금의 차이만 있을 뿐, 결국 프로토타입을 활용해서 만든 것이다.

 

1) Prototype based

  • Prototype based란, 생성자 함수를 통한 인스턴스 생성 즉, 원래의 자바스크립트에서의 인스턴스 생성 방식을 말한다.
  • 어떠한 특징을 가진 객체(프로토타입) 원형을 정의하고 해당 객체로부터 비슷한 객체들을 만들어 프로토타입으로 연결하여 사용한다.
    • 여기서의 상속 개념은 프로토타입을 연결하여 프로토타입 객체에서 필요한 정보를 가지고 오는 상속의 개념이다.

2) Class based

  • Class based란, 객체에 대한 클래스를 정의하고 클래스를 활용한 인스턴스를 생성하는 방식을 의미한다.
  • 어떤한 특징을 가진 객체에 대한 특징을 설계해 클래스를 정의하고 해당 클래스를 통하여 상속하여 사용한다.
    • 여기서의 상속 개념은 상속을 받은 부모 객체로부터 모든 데이터를 자식 객체에 복사해 자식 객체에서 사용한다.

 

3) Prototype based, Class based의 프로토 타입 관계

(왼쪽) Prototype based, (오른쪽) Class based

위의 프로토타입 관계에 따른 구조를 보았을 때 사실상, 내부적으로 프로토 타입과 클래스의 큰 차이점은 없다. 즉 ES6에서 도입된 Class는 생성자 함수와 프로토 타입의 관계에 대한 것을 클래스로 바꾸어 표현한 Syntactic Sugar이다. 

 

 

일반 생성자 함수와, 클래스로 객체를 만들었을 때 똑같은 결과가 나오는 것을 확인할 수 있다.

  // Prototype based
  function Person(name) {
    this.name = name;
  }

  Person.prototype.say = function () {
    console.log(`Hi, My name is ${this.name}.`);
  };

  const person = new Person("John");
  person.say();
  console.log(person);

  // ============================================================
  // Class based
  class Person2 {
    constructor(name) {
      this.name = name;
    }

    say() {
      console.log(`Hi, My name is ${this.name}.`);
    }
  }

  const person2 = new Person2("John");
  person2.say();
  console.log(person2);

 

단순히 상속을 구현하기 전까지는 별다를 게 없어 보인다.

2. Class

  • 기존의 프로토타입 기반의 구현은, 다른 언어에서의 Class 기반처럼 구현하기에는 까다롭고 사실상 동작 방식이 다르므로 ES6에서 class가 나옴.
  • 프로토타입 기반의 구현과 조금 다른 점은 있지만 내부적으로 프로토타입을 활용한다.
  • Class에서만 프로퍼티에 대해 private field 처리가 가능하다.
  // Class based
  class ClassPerson {
    // Field
    name;
    age;
    hobby;

    constructor(name, age, hobby) {
      this.name = name;
      this.age = age;
      this.hobby = hobby;
    }

    // Method1 함수 선언식
    showName() {
      console.log(this.name);
    }

    // Method2 함수 표현식
    showAge = function () {
      console.log(this.age);
    };

    // Method3 화살표 함수
    showHobby = () => {
      console.log(this.hobby);
    };
  }

  // Prototype Based
  function PrototypePerson(name, age, hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
  }

 

클래스는 기존의 생성자 함수처럼 객체 인스턴스를 만들기 위해 사용한다.

  // 인스턴스 생성
  const classPerson = new ClassPerson("John", 20, "soccer");
  const prototypePerson = new PrototypePerson("John", 20, "soccer");
  console.log(classPerson);

 

class로 생성한 인스턴스를 확인해 보면, 메서드의 3가지 선언 방식에 따라 아래와 같은 차이가 난다. 함수의 이름을 만들고 대입 "="을 한, 함수 표현식과, 화살표 함수의 경우 인스턴스 자체가 해당 메서드를 포함하고 있지만, 함수 선언식의 경우 인스턴스의 프로토타입 객체에 저장되게 된다.

이 말인즉슨, age, hobby 등은 인스턴스의 정보에 따라 달라지므로 인스턴스가 가져야 하지만, 해당 함수들은 고정적인 기능을 가지므로 인스턴스마다 생성할 필요가 없다. => 즉 프로토타입 객체에 한 번만 생성되는 게 좋다. 해서 class에서 method를 생성할 경우에는 함수 선언식으로 생성하는 것이 좋다.

 

 

즉 이것을 프로토타입 기반으로 생각했을 때는 아래와 같다.

  // Prototype Based
  function PersonFunction(name, age) {
    this.name = name;
    this.age = age;

    this.showAge = function () {
      console.log(this.age);
    };

    this.showHobby = () => {
      console.log(this.hobby);
    };
  }

  PersonFunction.prototype.showName = function showName() {
    console.log(this.name);
  };

 

위의 메서드들을 인스턴스에서 찍어보면 아래와 같이 나온다. 또한 서론에서 봤던 이미지처럼 프로토 타입 객체가 가리키는 constructor에 차이가 있다. 일단 기본적으로 객체 인스턴스가 가리키는 프로토타입 객체의 constructor가 Class는 class 자체를 가리키고, prototype은 생성자 함수를 가리킨다. 이때 자바스크립트는 프로토타입 기반이기 때문에 프로토타입을 활용한다는 점은 같다.

(위) Class based, (아래) Prototype based

2) private

기존의 생성자 함수를 통한 구현은 프로퍼티에 대해 private를 구현할 때 클로저를 활용해 할 수 있다.

  // Prototype Based
  function PersonFunction(name, age) {
  
    // private
    var name = name;
    this.age = age;

    this.getName = function () {
      return name;
    };
  }

  const personFunction = new PersonFunction("John", 20);
  console.log(personFunction);
  console.log(personFunction.getName());

 

class에서는 필드의 앞에 "#"을 붙여 private을 구현할 수 있다.

  // Class based
  class Person {
    #name;
    age;

    constructor(name, age) {
      this.#name = name;
      this.age = age;
    }

    getName() {
      return this.#name;
    }
  }

  const person = new Person("John", 20);
  console.log(person);
  console.log(person.getName());

3) this

자바스크립트에서의 this는 자신이 속한 객체 또는 생성할 인스턴스를 뜻한다. 기본적으로 클래스에 대한 인스턴스를 생성할 때 생성자에 프로퍼티(필드)들을 초기화해주기 위한 용도로 사용한다.

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

 

또한 어디에도 속하지 않은 최상위, 글로벌 컨텍스트에서 this를 사용하면 이는 전역 객체인 window를 가리킨다.

console.log(this);

 

프로토 타입에 화살표 함수를 사용해서 메서드를 정의하는 경우 일반 함수는 함수를 호출할 때 어떻게 호출되었는지에 따라 this에 바인딩 할 객체가 동적으로 결정되지만, 화살표 함수는 함수를 선언할 때 this에 바인딩 할 객체가 정적으로 결정된다. 화살표 함수의 this는 언제나 상위 스코프의 this를 가리킨다.

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

  Person.prototype.showName = () => {
    console.log(this.name);
    console.log(this);
  };

  let person = new Person("John");
  person.showName();

즉, 해당 this는 window 객체를 가리키기 때문에 제대로 동작하지 않는다.

 

 

하지만 아래와 같이 클래스의 경우에는 화살표 함수의 상위 스코프는 Person을 가리키기 때문에 this를 활용 가능하다.

  class Person {
    name;

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

    showName = () => {
      console.log(this.name);
      console.log(this);
    };
  }

  let john = new Person("John");

  john.showName();

5) super

  • Parent를 상속받은 Child 클래스의 constructor에서 this 키워드가 사용되기 전 super()를 호출하지 않으면 에러가 발생한다.
  • 혹은 자식의 컨스트럭터에서 super 키워드 하나만 사용될 수 있다.
  • 또는 super 키워드는 부모 객체의 함수를 호출하는 데 사용할 수 있다.
  class Parent {
    name;
    age;
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }

    method = () => {
      console.log(this.name);
    };
  }

  class Child extends Parent {
    hobby;
    constructor(name, age, hobby) {
      super(name, age);
      this.hobby = hobby;
    }
  }

  let parent = new Parent("parent", 50);
  let child = new Child("child", 20, "soccer");

  console.log(parent);
  console.log(child);
  child.method();

아래와 같은 구조로 만들어진다.

6) getter, setter

클래스의 프로퍼티에 대해서 값을 가지고 오고, 수정할 수 있는 getter, setter의 개념이 존재한다.

 

아래의 #을 사용한 private는 ECMAScript 2022부터 도입된 최신 문법이다.

  class Person {
    #name;
    age;

    constructor(name, age) {
      this.#name = name;
      this.age = age;
    }

    getName() {
      return this.#name;
    }

    setName(name) {
      this.#name = name;
    }
  }

  let john = new Person("John", 20);

  console.log(john);
  console.log(john.getName());
  john.setName("Bob");
  console.log(john.getName());

 

이는 아래처럼 구현할 수도 있다.

  class Person {
    #name;
    age;
    constructor(name, age) {
      this.#name = name;
      this.age = age;
    }

    get name() {
      return this.#name;
    }

    set name(name) {
      this.#name = name;
    }
  }
  
  let john = new Person("John", 20);
  console.log(john.name);

  john.name = "Bob";
  console.log(john.name);

 

3. 클래스와 프로토타입

결론적으로 클래스와 프로토타입은 상속에 있어서 아래와 같은 중요한 차이점을 가진다.

  class ClassParent {
    name;
    age;
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }

    method = () => {
      console.log(this.name);
    };
  }

  class ClassChild extends ClassParent {
    hobby;
    constructor(name, age, hobby) {
      super(name, age);
      this.hobby = hobby;
    }
  }

  let classParent = new ClassParent("parent", 50);
  let classChild = new ClassChild("child", 20, "soccer");
  console.log(classChild);
  classChild.method();

  // ====================================================================================
  function PrototypeParent(name, age) {
    this.name = name;
    this.age = age;

    this.method = function () {
      console.log(this.name);
    };
  }

  let prototypeParent = new PrototypeParent("parent", 50);

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

  PrototypeChild.prototype = prototypeParent;

  let prototypeChild = new PrototypeChild("child", 20, "soccer");

  console.log(prototypeChild);
  prototypeChild.method();

 

  • 메서드와 프로퍼티의 상속에 있어서 클래스는 복사의 개념, 프로토 타입은 연결(프로토타입 체인)의 개념으로 동작한다.
  • 클래스는 자식 클래스의 super()로 부모 클래스의 생성자를 사용하여 복사하지만,
  • 프로토타입은 자식 프로토타입에서 해당 프로퍼티의 이름으로 재지정 해주어야 한다.

728x90
반응형
Comments