• [JS] 객체의 상속

    2019. 11. 5.

    by. 나나 (nykim)

    출처: https://www.zerocho.com/category/JavaScript/post/573d812680f0b9102dc370b7

     

     

     

    프로토타입 체인을 이용하면 부모가 가진 기능에 더해 새로운 기능을 추가할 수 있습니다.

    SASS의 @extend와 비슷한 기능이죠.

     

    우선 생성자 하나를 만들어봅니다. 생성자 함수는 대문자로 시작했죠?

     

    function Vehicle(name, speed){
      this.name = name;
      this.speed = speed;
    }

     

    이제 new Vehicle()을 통해 고유의 name과 speed를 갖는 객체를 만들 수 있게 됐습니다.

     

    그런데 이 생성자 함수를 통해 만들어진 객체들에게 공통적인 기능(함수)을 추가하려고 해요.

    그럴 때는 this를 쓰는 대신, prototype에 메서드로 추가하는 게 낫죠.

     

    Vehicle.prototype.drive = function () {
      console.log(`${this.name}은(는) ${this.speed}의 속도로 달립니다.`);
    }

     

    이렇게 하면 Vehicle()의 프로토타입 객체에 drive라는 메서드가 추가됩니다.

    여기서 잠깐 복습! 메서드 내의 this는 어디에 바인딩 된다고 했죠? 바로 메서드를 호출한 객체로 바인딩됩니다.

     

    이제 이 생성자 함수를 통해 새로운 객체를 만들어봅니다.

    경차 하나를 만들어볼게요.

     

    var tico = new Vehicle("tico", 50);
    tico.drive(); //"tico은(는) 50의 속도로 달립니다."

     

    잘 달리네요 :D

     

    이번엔 세단을 한 번 만들어볼까 합니다.

    어쨌든 얘도 자동차니까 new Vehicle()을 통해 만들 거긴 한데, 좀 특별한 부스트 기능을 넣고 싶어요!

    그럴 때 쓰는 게 상속입니다.

     

    function Sedan(name, speed, maxSpeed) {
       this.name = name;
       this.speed = speed;
       this.maxSpeed = maxSpeed;
    }

     

    Sedan()이라는 새로운 생성자 함수를 만들었습니다.

    인자로 name, speed, maxSpeed 세 개를 받네요. 그런데 이 중 두 개는 Vehicle()과 동일합니다.

    즉, this.name = name; this speed = speed; 부분을 다시 써주는 건 비효율적입니다.

    이 두 속성은 Vehicle()로부터 가져오고, maxSpeed만 따로 넣어주려고 합니다.

     

    이건 apply()라는 메서드를 통해 구현할 수 있습니다.

     

    function.apply(thisArg, [argArray])

     

    apply()는 함수를 호출하여, 1번째 인자를 함수 내부의 this로 바인딩 하고, 2번째 인자를 호출한 함수의 인자로 넘기는 기능이 있습니다.

    잠깐만 예제를 살펴보죠.

     

    var person = {
      whois: function (country) {
        console.log(this.firstName + " " + this.lastName + " from " + country);
      }
    }
    
    var nana = {
      firstName: "nana",
      lastName: "kim"
    }
    
    person.whois.apply(nana, ["korea"]);

     

    위 코드에선 person.whois()이라는 함수에 apply를 호출했습니다.

    그리고 this의 내용을 nana 객체의 것으로 대체했음을 알 수 있습니다.

     

    본론으로 돌아와서, 그럼 Vehicle()의 속성을 받아오려면 어떻게 해야할까요?

     

    function Sedan(name, speed, maxSpeed) {
      Vehicle.apply(this, arguments);
      this.maxSpeed = maxSpeed;
    }

     

    여기서 arguments는 넘어온 인자를 나타내는 유사배열입니다.

    즉, Vehicle이라는 함수에다 this를 바인딩하고, 그 인자는 arguments로 하겠다~라는 소리네요.

     

    새 차를 만들어볼까요?!

     

    var sonata = new Sedan("sonata", 100, 200);

     

    Sedan()이 실행되면, Vehicle.apply()를 통해 Vehicle 함수가 실행됩니다.

    이때 Vehilce()함수는 두 개의 인자를 필요로 하는데, 그걸 this(여기선 sonata)로부터 받아오게 합니다.

    구체적인 인자는 arguments 안에 들어있고요.

     

    그래서 콘솔에 찍어보면...

     

    Sedan {
      name: "sonata",
      speed: 100,
      maxSpeed: 200,
      __proto__: Object
    }
    

     

    Sedan()만 호출했을 뿐인데도 새 Vehicle이 잘 탄생했네요!

     

     

    이제 소나타로 달려볼까요?!

     

    sonata.drive(); //Uncaught TypeError: sonata.drive is not a function

     

    엌 에러가 뜨네요ㅜㅜ

    왜냐, drive() 메서드는 Vehicle의 prototype 객체에 들어있습니다.

    하지만 sonata가 __proto__로 연결하고 있는 prototype 객체는 Vehicle.prototype이 아닌, Sedan.prototype입니다.

     

    sonata.__proto__ === Vehicle.prototype //False
    sonata.__proto__ === Sedan.prototype //True

     

     

    따라서 Sedan.prototype을 손봐줄 필요가 있습니다.

     

    Sedan.prototype = Object.create(Vehicle.prototype);

     

    Object.create()는 지정된 프로토타입 객체와 속성을 갖는 새로운 객체를 만듭니다. (MDN)

    그래서 Vehicle.prototype을 프로토타입 객체로 갖는 새 객체를 만들어, Sedan.prototype에 넣어준 셈입니다.

    Object.create는 객체를 만들되 생성자는 실행하지 않습니다.

     

    Sedan.prototype === Vehicle.prototype //False

     

    Sedan.prototype = Vehicle.prototype처럼 바로 넣어준 게 아니라,

    새 객체를 만들어넣어줬기 때문에 엄밀히 말하면 두 함수는 서로 다른 프로토타입 객체를 갖습니다.

    다만 Vehicle.prototype의 내용을 Sedan.prototype이 가지게 된 것 뿐이죠.

     

    그런데, 뜨든. 한 가지 문제가 있습니다.

     

    Sedan.prototype.constructor === Vehicle //True

     

    prototype.constructor는 당연히 그 생성자를 가리켜야 하는데,

    Sedan의 생성자는 Vehicle을 가리키고 있네요 켘

    틀린 점은 고쳐주고 넘어갑시다.

     

    Sedan.prototype.constructor = Sedan;

     

    그리고 Sedan만의 특별한 기능을 추가해줍니다. 아까 넣으려고 했던 부스트 기능이요!

     

    Sedan.prototype.boost = function () {
      console.log(`부스트!!! 이제 ${this.name}은(는) ${this.maxSpeed}의 속도로 달립니다.`);
    }

     

    그럼 이제 세단이 잘 동작하는 걸 볼 수 있습니다.

     

    var sonata = new Sedan("sonata", 100, 200);
    sonata.drive(); //"sonata은(는) 100의 속도로 달립니다."
    sonata.boost(); //"부스트!!! 이제 sonata은(는) 200의 속도로 달립니다."

     

     

    응용하면 짐을 싣는 트럭도 만들어볼 수 있겠네요!

     

    function Truck(name, speed, capacity) {
      Vehicle.apply(this, arguments);
      this.capacity = capacity;
    }
    
    Truck.prototype = Object.create(Vehicle.prototype);
    Truck.prototype.constructor = Truck;
    Truck.prototype.load = function (weight) {
      if (weight > this.capacity) {
        return console.error(`${weight - this.capacity}만큼 중량 초과입니다.`);
      }
      console.log(`${weight} 만큼의 짐을 실었습니다.`)
    }
    
    var boongboong = new Truck("boongboong", 40, 100);
    
    boongboong.drive(); //"boongboong은(는) 40의 속도로 달립니다."
    boongboong.load(130); //"30만큼 중량 초과입니다."

     

     

     

    +

    개인적으로 공부한 내용이라 틀린 점이 있을 수 있습니다.

    더 정확한 내용을 알려주신다면 감사하겠습니다 :)

     

     

     

    댓글 0