ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 프로토타입으로 상속 구현하기
    JavaScript 2022. 2. 19. 01:07

    VanillaJs로 채용과제를 약 일주일정도 진행하게 되었습니다.
    이때  클래스 키워드를 사용하지 않고 function으로 만들게 되었는데, prototype의 활용을 잘 하지 못한것 같아 아쉬움이 남았습니다.

    따라서 이번에 prototype을 통한 상속을 구현해 보면서 개념을 정리하고, 추후에 리팩토링을 해보려 합니다.

     

     

    생성자 함수 만들기

    function Foo(name) {
      this.name = `${name} success!`;
      console.log("constructor Foo");
    }
    
    Foo.prototype.getName = function () {
      console.log(this.name, "prototype");
    };

    기본적인 함수를 만들어 주었습니다. 

    화살표 함수로 만들어주게 된다면 this binding을 할 수 없고, 생성자 함수로 호출 할 수 없기 때문에 함수 선언문을 사용하였습니다.

     

    저는 Bar라는 함수가 Foo 함수를 상속받았으면 좋겠다는 생각을 했습니다. 

    어떻게 구현해야 할까요?

     

     

     

    상속 구현하기

    function Foo(name) {
      this.name = `${name} success!`;
      console.log("constructor Foo");
    }
    
    Foo.prototype.getName = function () {
      console.log(this.name, "prototype");
    };
    
    function Bar(name) {
      this.name = name;
    }
    
    function extend(superFunc, subFunc) {
      function F() {}  // 1
      F.prototype = Foo.prototype;  // 2
      subFunc.prototype = new F();  // 3
      subFunc.prototype.constructor = subFunc; // 4
    }

     

    extend함수로 상속을 구현해 보았습니다. extends 키워드이기 때문에 s를 빼고 네이밍을 지어주었습니다.

     

    1번

    비어있는 함수 F를 정의했습니다. 앞으로 왜 만들어 주었는지 확인해 보겠습니다.

    2번

    F의 프로토타입을 Foo의 프로토타입으로 지정해 주었습니다.

    즉, F의 인스턴스들은 __proto__ 를 통해 이제부터 Foo함수의 constructor메서드가 아닌 Foo에서 prototype으로 정의한 메서드들에 접근할 수 있습니다. 

    아래의 사진은 3번을 나타낸것입니다.

    3번

    subFunc의 프로토타입에 F함수의 인스턴스를 할당해 주었습니다.

    여기까지 본다면 subFunc의 프로토타입은 F함수의 인스턴스와 연결되어있습니다.

    3번에서 우리는 F함수의 인스턴스가 Foo에 접근할 수 있는것을 알 수 있습니다.

    따라서 우리는 subFunc의 프로토타입을 통하여 F함수의 prototype을 거쳐 Foo함수의 prototype까지 접근할 수 있습니다.

    4번

    함수의 prototype 프로퍼티는 생성 시점에 constructor프로퍼티만을 가지고 있습니다. constructor는 자신과 연결된 (생성자) 함수를 가리키며 이 프로퍼티를 통해 어떤 (생성자)함수를 통해 만들었는지 알 수 있습니다.

    constructor가 Foo함수를 참조하기 때문에 인스턴스가 Foo함수를 통해 만들어졌다는것을 알 수 있다.

    하지만 Bar의 prototype은 현재 F의 인스턴스이기 때문에 constructor 프로퍼티가 존재하지 않습니다. 

    따라서 Bar.prototype.constructor를 자기 자신으로 하여 순환참조를 할 수 있도록 합니다.

     

     

     

    생성자 함수 호출하기

    여기까지 했다면 모든 구현이 다 끝났지만 class 문법에서 생성자 함수는 인스턴스 생성 시 가장 먼저 호출이 됩니다.

    하지만 위의 함수에서는 (생성자) 함수 Foo를 호출하고 있지 않습니다. 

    따라서 apply문법을 통해 호출합니다.

     

    function Bar(name) {
      Foo.apply(this, arguments);
      this.name = name;
    }

    apply문법은 Foo함수를 간접적으로 호출할 수 있게 해 줍니다. 두번째 인자로, Foo함수에 들어갈 인자를 배열 형태로 넣어줍니다.

    함수 선언문에는 arguments객체가 존재하기 때문에 편리하게 넣어줄 수 있습니다.

     

    코드

    function Foo(name) {
      this.name = `${name} success!`;
      console.log("constructor Foo");
    }
    
    Foo.prototype.getName = function () {
      return `${this.name} prototype`;
    };
    function Bar(name) {
      Foo.apply(this, arguments);
      this.name = name;
    }
    
    function extend(parent, child) {
      child.prototype = Object.create(parent.prototype);
      child.prototype.constructor = child;
    }
    
    extend(Foo, Bar);
    const a = new Bar("Jin"); // constructor Foo
    console.log(Bar.prototype); // Foo { constructor: [Function: Bar] }
    
    //Bar의 인스턴스의 a는 어떤 함수로 만들어졌는지 확인하기
    console.log(a.__proto__.constructor); [Function: Bar]
    console.log(a.getName()); //Jin prototype
    
    // Foo함수의 this.name = `${name} success!`;이 오버라이딩되어 사라진것을 알 수 있습니다.
    console.log(a.name); // Jin
    console.log(a instanceof Foo); // true
    console.log(a instanceof Bar); // true

     

    다음으로 Object.create를 사용하여 약간의 코드를 수정합니다.

     

    Object.create메서드의 경우 지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듭니다.

    function F() {}
    F.prototype = parent.prototype;
    child.prototype = new F();
    
    
    // 위의 코드를 아래로 변경할 수 있습니다.
    child.prototype = Object.create(parent.prototype);

     

     

     

    최종 코드

    function Foo(name) {
      this.name = `${name} success!`;
      console.log("constructor Foo");
    }
    
    Foo.prototype.getName = function () {
      return `${this.name} prototype`;
    };
    function Bar(name) {
      Foo.apply(this, arguments);
      this.name = name;
    }
    
    function extend(parent, child) {
      child.prototype = Object.create(parent.prototype);
      child.prototype.constructor = child;
    }
    
    extend(Foo, Bar);
    const a = new Bar("Jin"); // constructor Foo
    console.log(Bar.prototype); // Foo { constructor: [Function: Bar] }
    
    //Bar의 인스턴스의 a는 어떤 함수로 만들어졌는지 확인하기
    console.log(a.__proto__.constructor); [Function: Bar]
    console.log(a.getName()); //Jin prototype
    
    // Foo함수의 this.name = `${name} success!`;이 오버라이딩되어 사라진것을 알 수 있습니다.
    console.log(a.name); // Jin
    console.log(a instanceof Foo); // true
    console.log(a instanceof Bar); // true

     

    댓글

Designed by Tistory.