본문 바로가기
Dev log

JavaScript 다중상속에 대한 고찰

by Pig_CoLa 2020. 8. 2.
SMALL

JavaScript는 객체지향에 맞게 구성할 수 있음에도 불구하고

Class기반 언어가 아닌 prototype기반 언어이기에

완벽한 객체지향이 아니라고 부르는 사람도 있다.

 

그중 하나의 키워드가 다중상속 이다.

다중상속이 무엇인가

  • A클래스
    • A를 상속받은 B클래스
      • B를 상속받은 C클래스
  • D클래스
    • D를 상속받은 E클래스
      • E를 상속받은 F클래스

이때 C클래스와 F클래스 모두를 상속받는 G클래스가 있다면 G클래스는 다중상속을 하고있는 것이다.

 

하지만 prototype에 대한 이해도가 어느정도 있다면 여러가지 편법을 동원해서

다중상속을 구현할 수 있을것이라고 생각하게 되었다.

 

이 아래에서 등장하는 여러 명칭은 Python과 JavaScript가 섞여있다.
필자도 이를 착각하고 혼용하는 경우가 많으니 잠시 설명하고 넘어가겠다.

  • attribute === property : 속성 이라는 뜻으로 python에선 attribute, JavaScript에선 property라고 부른다.
  • mro === prototype-chain : 상속관계를 확인하는 가장 중요한 요인이다.

사고 구성

다중상속후 생성된 객체에서 적용될 규칙을 구성한다.

(이 규칙은 필자가 정한것이 아닌 Python의 다중상속 규칙을 분석한 것일 뿐이다.)

 

  1. 다중상속시 우선순위 되는 class가 있다.
  2. 상속받은 class에 없는 메서드 호출시 우선순위가 되는 class의 상속관계 라인에서 먼저 확인한다.(JavaScript로 치자면 prototype-chain을 확인하는 것이다. Python에선 이를 mro라고 한다.)
  3. 없을경우 후순위 class의 mro를 확인한다.

최대한 위 규칙에 위배되지 않는 선에서 prototype과 여러 편법을 사용해서 구성해보는 것이 목표였다.

property

여러 고민들을 거친채 코드를 구현해 내가기 시작했다.

그중 `상속대상 두 class가 각각 생성해낼 property(attribute)를 상속받은 class내에서 만들어 내야한다.

이또한 우선순위가 높은 class를 나중에 평가하게 하여 겹치는 property에 대해서도 우선순위를 가져가게 해야했다.

문제 발생

ES6 문법에서 강제로 다중상속을 구현하려 하다보니 큰 문제가 생겼다.

super키워드는 오로지 하나의 상속대상에 대하여 호출기때문에 불가능하여

다중상속을 위해선 ES5문법을 사용해야 했는데

ES5 문법에서 ES6의 class를 상속받을 경우 call을 통한 호출이 불가능했다.

해결 방안

super키워드를 마음대로 다시 정의해 줄 수는 없기 때문에

ES5 문법으로 ES6의 class가 this를 바인딩 한채로 호출되도록 하는 편법을 사용할 수 밖에 없었다.

이것저것 시도해보다가 결국 class를 통해 생성된 instance도 object이기 때문에

이를 순회하여 생성할 this에 넣어주면 되겠다 라는 생각을 하게되었다.

class A { // ES6 문법으로 class키워드를 사용해 클래스를 만들었다.
  constructor(name) {
    this.name = name;
  }

  hi() {
    return `hi~ my name is ${this.name}`;
  }
}

function B(name, age) { // 이때에 ES5문법으로 이를 상속받아야 한다.
  let temp = new A(name);
  for (let i in temp) {
    this[i] = temp[i];
  }
  this.age = age;
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

let a = new B("Hwang", 26);
console.log(a) // B {name: "Hwang", age: 26}
a.hi() // "hi~ my name is Hwang"

prototype에 대한 편법

상속받은 입장에서 중요한것은 SuperClass에서 생성할 property를 가지고 있는것 뿐만 아니라

메서드, 상속관계 확인등 추가적인 조치를 위해 여전히 prototype을 수정해줘야 한다.

 

이때 prototype을 어떻게 변경해야 원하는 대로 작동할지에 대한 고민만 3일은 한듯하다.

 

우선순위인 SuperClass, 후순위인 OtherSuperClass가 있다고 할때

SuperClass prototype-chain에 존재하지 않는 메서드만 OtherSuperClass의 prototype-chain에서 사용되어야 한다.

문제 발생

prototype도 결국 Obejct이기때문에 key값을 얻어올 수 있을거라고 생각했지만

열거속성이라는것이 false라면 keys, values, for ...in문 등등으로 키나 값을 얻어올 수 없었다.

원래 생각은

for (let i in OtherSuperClass) {
    if (!(i in SuperClass)) {
        SuperClass[i] = OtherSuperClass[i]
    }
}

분명 이렇게 하면 SuperClass의 prototype-chain에

존재하지 않는 메서드일때만 추가될 것이라고 생각했다.

문제의 원인

prototype-chain에서 엮인 메서드도 찾는것을 확인했다.

class A{
    constructor() {
        this.i = 3;
    }
    hi() {
        console.log('hi')
    }
}

class AA extends A{
    constructor() {
        super()
    }
}

'hi' in AA.prototype // true

하지만 for ...in문의 반복을 사용할 수 없던것이 가장 큰문제...

찾아보니 prototype의 속성들은 전부 열거가능하지 않은 속성들 이기 때문이었다.

열거가능한지를 확인하기 위해선 아래와 같다

Object.getOwnPropertyDescriptor(Object.prototype, 'toString');
// {writable: true, enumerable: false, configurable: true, value: ƒ}
// enumerable속성이 false라면 열거가능하지 않다.

해결 방안

열거가능하지 않은 속성들을 받아와야 하기 때문에 Object의 메서드들을 살펴보니

getOwnPropertyNames메서드가 있었고, 원하던 열거불가능한 속성을 포함한 'key'값들이 Array에 들어가 있는 형태로 돌려준다.

Reference

열거 속성

getOwnPropertyNames

 

이를 활용하여 코드를 다시 작성하였다.

mixin = function (SuperClass, OtherSuperClass) {
  let result = Object.create(SuperClass.prototype);
  recur(result, Object.create(OtherSuperClass.prototype).__proto__)
  return result;

  function recur(A, B) {
    if (B.__proto__.constructor === Object) { return; }
    let temp = Object.getOwnPropertyNames(B);
    for (let i of temp) {
      if (!(i in A)) {
        A[i] = B[i];
      }
    }
    recur(A, B.__proto__);
  }
}

이와 같은 방법을 사용하면 SuperClass의 메서드를 우선적으로 탐색한 후
없을경우 OtherSuperClass의 메서드를 사용하는것과 같게 된다.

prototype에 대한 근본적인 문제

이와 같은 편법을 썼지만 prototype기반 언어의 근본적인 문제로

prototype-chain이 올바르게 이루어지지 않는다면 상속관계 확인이 원활하게 이루어 지지 않는다.

(현재 방법으로는 SuperClass에 대한 상속관계만 정확하게 표기된다.)

 

하지만 이는 prototype과 타 언어의 mro구성에 관해 정확한 이해가 동반된다면

분명한 상속관계 구현까지 가능할 것이라고 생각한다.

앞으로의 과제

SuperClass와 OtherSuperClass를 다중상속받는 Class를 상속관계까지 올바르게 구현하기.

LIST

댓글