본문 바로가기
JavaScript

자바스크립트 상속 그리고 프로토타입이란

by 붕어사랑 티스토리 2023. 1. 31.
반응형

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

 

Inheritance and the prototype chain - JavaScript | MDN

JavaScript is a bit confusing for developers experienced in class-based languages (like Java or C++), as it is dynamic and does not have static types.

developer.mozilla.org

 

 

 

1. 개요

상속의 관점에서 자바스크립트는 자바나 C++처럼 일반적인 객체지향 언어와 조금 다릅니다.

자바스크립트는 기본적으로 Object만 가지고 있습니다. 클래스의 정의도 보면 Object의 템플릿 역할을 한다고 나와있지요.

 

 

자바스크립트의 오브젝트는 각각 다른객체를 참조하는 프로퍼티를 가지고 있는데, 이를 프로토타입 이라고 부릅니다. 프로토탑은 계속해서 channing 되어 null이 이 될 때 까지 이어집니다.

 

자바스크립트는 이러한 구조때문에 static dispatch가 없습니다. dynamic dispatch만 존재합니다

 

 

Static Dispatch, Method Dispatch?

이 개념을 알려면 먼저 Method Dispatch라는 개념을 알아야 합니다. Method Dispatch란 클래스 다형성에서 어떤 메소드를 사용할지를 찾는 과정입니다.

 

먼저 아래 코드를 봅시다.

class A {
	void print(){
    	printf("class A");
    }
}

class B extends A {
	void print(){
    	printf("class B");
    }
}

A a = new B();
a.print();

 

위 코드를 보면 print함수는 class B 를 출력 할 것입니다. 런타임 시점에서 A의 print를 호출할지 B의 print 호출할지를 결정하는 것 이지요.

 

  • 이처럼 일반적인 객체지향 함수에서 다형성을 위해 런타임에서 어떤 함수를 호출할 지 결정하는 것을 Dynamic Dispatching 이라고 합니다
  • 반대로 컴파일 시점에서 어떤함수를 호출할 지 결정하는 것을 컴파일 타임에서 결정하는것을 Static Dispatching이라고 합니다

상속가능성이 없거나 상속할 생각이 없다는 키워드를 전달해주면 컴파일러가 알아서 static dispatching 해 주겠지요?

그리고 기본적으로 Static Dispatching이 속도가 더 빠르므로 최적화 할 때 신경 써 줍시다.

 

 

 

 

2. 자바스크립트의 상속은 프로토타입 체인 형태로 이루어진다

앞선 내용에서 자바스크립트는 static dispatching이 없고 dynamic dispatching으로 이루어지는 것을 알았습니다.

자바스크립트에서는 오브젝트의 프로퍼티를 찾을 때 dynamic dispatching 형태로 프로퍼티를 찾습니다.

 

아래와 같은 예시가 있다고 합시다.

 

myObject.A

 

  • A라는 프로퍼티를 찾기위해 먼저 myObject를 찾습니다.
  • myObject에 A가 없을 시, 프로토타입으로 올라가 A를 찾습니다
  • 프로토타입에서도 A가 없으면 체이닝을 하여 프로토타입의 프로토타입으로 올라가 A를 찾습니다

 

 

프로토탑의 정의방법은 여러가지가 있지만 기본적인 방법 __proto__를 이용하는 예제를 확인해보겠습니다.

b가 자기자신과 프로토타입에 둘다 정의되어 있을시 자기자신의 b를 출력하네요.

const o = {
  a: 1,
  b: 2,
  __proto__: {
    b: 3,
    c: 4,
  },
};

console.log(o.a); // 1
console.log(o.b); // 2
console.log(o.c); // 4

console.log(o.d); // undefined

 

아래처럼 프로토탑의 프로토타입도 정의해 줄 수 있습니다. d를 찾기위해 조상의 조상까지 건너갔네요.

const o = {
  a: 1,
  b: 2,
  __proto__: {
    b: 3,
    c: 4,
    __proto__: {
      d: 5,
    },
  },
};

// { a: 1, b: 2 } ---> { b: 3, c: 4 } ---> { d: 5 } ---> Object.prototype ---> null
console.log(o.d); // 5

 

 

 

 

3. "메소드" 상속

먼저 알아야할 사실은 자바스크립트는 "메소드"라는 개념은 다른 언어들의 메소드와 조금 다릅니다. 자바스크립트는 함수형 언어로 함수는 하나의 객체로 취급되고, 오브젝트에게는 하나의 프로퍼티로 주어집니다. 그리고 다른 프로퍼티들과의 동작에서 큰 차이는 없습니다.

 

아래는 자바스크립트에서 오버라이딩 형태의 동작을 보여주는 예시입니다.

 

const parent = {
  value: 2,
  method() {
    return this.value + 1;
  }
};

console.log(parent.method()); // 3
// 부모가 method를 부르면, 여기서의 this는 부모를 가리킵니다

// 자식 오브젝트는, 부모 오브젝트를 상속받습니다.
const child = {
  __proto__: parent,
};
console.log(child.method()); // 3
// 자식이 method를 호출하면 부모까지 올라가 method를 호출합니다
// 여기서 this는 자식이 호출했으니 자식의 this를 가리킵니다.
// 자식의 this에는 value가 없기에 프로토타입 체이닝이 일어나 부모의 value를 읽어오게 됩니다
// 부모의 value값은 2이기에 3을 리턴합니다.

child.value = 4; 
// 자식의 오브젝트에 value를 생성하였습니다.
// 부모의 value는 자식의 value에 의해 가려지게 됩니다.
// 이제 자식의 오브젝트 형태는 아래와깉이 되겠습니다
// { value: 4, __proto__: { value: 2, method: [Function] } }

console.log(child.method()); // 5
// 위의 코드와 다르게 자식에게는 value가 있으므로 this.value는 자식의 value를 가리킵니다.
// 그러므로 5를 리턴합니다.

 

 

4. 프로토타입의과 메모리 절약

 

프로토타입의 가장 큰 장점은 메모리 절약이 가능하다는 것 입니다 (라고 공식문서에 나와있는데 다른언어들은 알어서 되는거 같은디..)

 

 

 

먼저 다음 코드를 보지요

const boxes = [
  { value: 1, getValue() { return this.value; } },
  { value: 2, getValue() { return this.value; } },
  { value: 3, getValue() { return this.value; } },
];

위 코드에서 오브젝트 배열안에 오브젝트들은 각각 값을 가지고 getter를 하나씩 가지고 있습니다.

여기서 getter들은 각각의 오브젝트에 귀속되고있네요. 그런데 하는 역할은 똑같습니다.

 

똑같은 기능을 하는데 메모리는 3배로 쳐묵쳐묵 하고있네요!

 

프로토타입을 이용하면 이를 1개로 줄일 수 있습니다

 

const boxPrototype = {
  getValue() { return this.value; },
};

const boxes = [
  { value: 1, __proto__: boxPrototype },
  { value: 2, __proto__: boxPrototype },
  { value: 3, __proto__: boxPrototype },
];

 

getter를 프로토타입으로 올리고, 각각의 자식들에게 __proto__로 연결시켜주니 이제 getter는 하나로 줄어들게 되었습니다. 이런식으로 자바스크립트는 프로토타입으 이용하여 메모리 관리를 할 수 있습니다

 

하지만 위 코드에도 문제가 있습니다. getter를 연결해주려고 __proto__ : boxPrototype이라는 똑같은 코드를 세번이나 작성해 주었네요.

 

이를 해결하는 방법은 다음과 같이 생성자 함수를 이용하는 것 입니다!

 

// 오브젝트를 생성해주는 생성자 함수 입니다
function Box(value) {
  this.value = value;
}

// Box생성자로 생성된 모든 오브젝트들은 프로토타입에 getValue를 가지게 됩니다
Box.prototype.getValue = function () {
  return this.value;
};

const boxes = [
  new Box(1),
  new Box(2),
  new Box(3),
];

 

 

 

그리고 위 형태를 클래스로 다시 작성하면 아래처럼 됩니다

class Box {
  constructor(value) {
    this.value = value;
  }

  // Methods are created on Box.prototype
  getValue() {
    return this.value;
  }
}

 

 

 

5. 클래스 상속시 프로토타입 체이닝

 

아래 처럼 클래스간 프로토타입 체이닝을 직접적으로 연결시켜 줄 수 있습니다

function Base() {}
function Derived() {}
// Derived 프로토타입을 Base로 연결
Object.setPrototypeOf(
  Derived.prototype,
  Base.prototype,
);

const obj = new Derived();
// obj ---> Derived.prototype ---> Base.prototype ---> Object.prototype ---> null

 

위코드는 아래처럼 상속을 이용한 것과 같습니다

 

class Base {}
class Derived extends Base {}

const obj = new Derived();
// obj ---> Derived.prototype ---> Base.prototype ---> Object.prototype ---> null

 

반응형

'JavaScript' 카테고리의 다른 글

자바스크립트 Promise에 대하여  (0) 2023.02.07
자바스크립트 this에 대하여  (0) 2023.02.02

댓글