[Javascript]Class와 Prototype

ybw·2021년 11월 18일
1

먼저, 자바스크립트에서 ClassPrototype에 대해서 설명하기 전에 중요한 사실을 짚고 넘어가겠습니다.

바로 자바스크립트에는 문법상으로는 Class가 존재하지만, 실제로는 Class가 존재하지 않는다는 사실입니다.

이게 과연 무슨 말인지 알아보기 위해, Class에 대해서 먼저 알아보겠습니다.

Class

Class는 일반적으로 객체지향 프로그래밍에서 객체를 찍어내는 틀을 의미합니다.

ECMAScript2015에 몇 가지 키워드가 도입되면서 Class도 같이 도입되었습니다.

하지만 이 때, 도입된 Class는 기존에 객체지향 프로그래밍언어에서의 Class와 다른 동작 방식을 가집니다. 즉, 사실 Javascript에서 ClassPrototype을 기반으로 다른 언어에서의 Class와 다르게 동작됩니다.

다음 아래 예시는 자바스크립트에서 Class가 사용된 예시입니다.

'use strict'

class Cadet {
  constructor(intraID) {
    this.intraID = intraID;
  }
}

이 코드는 문법상, 이렇게 작성되지만 실제로는

function Cadet(intraID) {
  this.intraID = intraID;
}

이런 코드로 바뀌게 됩니다.

그러면 아래와 같은 코드로 바뀌어 실행이 되면, return되는 값이 없는데 객체가 어떻게 생성되는지 의문이 드실겁니다.

javascript에서 함수는 new연산자를 만나게 되면, 신기한 현상이 발생하기 때문입니다. 먼저, 새로운 빈 객체를 메모리 상에 생성합니다. 이후, 빈 객체가 함수의 this에 바인딩 되고 this객체의 속성을 채우는 동작이 수행됩니다. 마지막으로 함수에서 return이 존재하지 않으면, this가 return 됩니다.

다음으로 클래스의 상속에 대해서 알아보겠습니다.

'use strict'

class Cadet {
  constructor(intraID) {
    this.intraID = intraID;
  }
}

class CadetStduyingJS extends Cadet{ // Cadet을 상속하는 CadetStduyingJS클래스
  constructor(intraID) {
    super(intraID);
  }
  
  doStudy() {
    console.log("i'm studying JS);
  }
}

일반적인 객체지향 프로그래밍에서 상속은 부모 클래스의 내용이 자식 클래스에 복사되어 상속하게 됩니다.

하지만 javascript에서는 이런 방법으로 상속이 이루어지지 않습니다.

javascript에서는 오로지 원시값과 객체의 참조값만 복사가 될 수 있어 부모 클래스의 내용이 자식 클래스의 내용에 복사가 될 수 없기 때문입니다.

따라서 Javascript에서는 클래스, 객체의 내용 복사 없이도 상속을 구현할 수 있게 해주는 Prototype을 사용합니다.

Prototype

Prototype은 다른 언어처럼 내용의 복사를 통해 이루어지는 상속이 아니라 객체와 객체간의 연결이라는 특성을 사용하여 javascript에서 상속을 구현하는 방법입니다.

아래 이미지는 브라우저 콘솔화면에서 확인할 수 있는 prototype입니다.


특정 객체의 프로토타입 객체에 바로 접근하는 공식적인 방법은 없습니다. — Javascript 언어 표준 스펙에서[[prototype]]으로 표현되는 프로토타입 객체에 대한 "링크"는 내부 속성으로 정의되어 있습니다. (ECMAScript 참조). 하지만 많은 수의 모던 브라우저들이 __proto__ (en-US) (앞뒤로 언더바 2개씩) 속성을 통해 특정 객체의 프로토타입 객체에 접근할 수 있도록 구현하였습니다. 예를 들자면 person1.__proto__또는 person1.__proto__.__proto__ 코드로 체인이 어떻게 구성되어 있는지 확인해 보세요!
MDN - ProtoType

객체와 객체를 연결하는 링크인 __proto__라는 속성을 사용하여 상속이 구현됩니다.

이런 객체와 객체를 연결하는 링크는 크게 3가지로 분류될 수 있습니다.

먼저, 다른 객체를 바탕으로 만들어진 객체라면, 객체는 자신의 원형이라고 할 수 있는 객체가 있다면 그 객체를 가리키는 __proto__링크를 자동으로 가집니다.

const newObj = Object.create(oldObj)
newObj.__proto__ === oldObj

다음으로 그냥 객체가 아니라 함수객체인 경우라면, 이 경우에는, __proto__이외로 해당 함수의 prototype객체가 추가로 만들어지집니다. 해당 함수의 prototype속성은 생성된 prototype 객체를 가리키고 prototype 객체는 constructor속성을 통해서 해당 함수를 가리키는 순환참조의 관계를 갖습니다.

마지막으로 new + 함수로 만들어진 객체라면, 생성된 객체의 __proto__링크가 해당 함수의 prototype객체를 가리키게 됩니다.

function sayJS() {
  console.log(`${this.intraID} saying JS!`);
}

function Cadet(intraID) {
  this.intraID = intraID;
}

Cadet.prototype.sayJS = sayJS;

const byoo = new Cadet('byoo');

byoo.sayJS();

그러면 다음과 같은 코드에서 마지막에 byoo객체가 sayJS함수를 호출할 때, 과연 어떤 일이 벌어지게 될까요?

이 때, 프로토타입 체이닝이라는 일이 일어나게 됩니다.

프로토타입 체이닝은 프로토타입을 __proto__링크를 사용하여 탐색해 나아가는 과정이 일어나게됩니다.

byoo객체에서 sayJS함수를 호출하면 먼저 byoo객체에 sayJS함수가 있는지 살펴보게 됩니다. sayJS함수는 byoo객체에 존재하지 않지만 종료되지 않고 byoo객체를 생성했던 Cadet함수의 Prototype객체로 __proto__링크를 통해 이동하고 그 곳에서 sayJS함수가 있는지 찾아봅니다. 이 때, sayJS함수가 있으므로 sayJS함수가 동작되게 됩니다.

프로토타입 체이닝은 prototype을 타고 들어가며, __proto__이 null값인 객체, 즉, 원시객체가 등장하게 되면, 이러한 prototype chaining이 멈추게 됩니다.

즉, javascript는 프로토타입 기반 언어(prototype-based language)입니다. 객체들 간에 상속 관계를 표현하기 위해 prototype을 사용하며, 다른 언어에서 사용되는 상속처럼 복사가 진핸되는 것이 아닌 prototype chain을 통해 연결된 객체를 찾아 상속이 진행됩니다.

그러면 코드를 조금 수정해서 아래와 같이 고치면 어떻게 될까요?

function sayJS() {
  console.log(`${this.intraID} saying JS!`);
}

function Cadet(intraID) {
  this.intraID = intraID;
}

Cadet.prototype.sayJS = sayJS;

const byoo = new Cadet('byoo');

byoo.sayJS = function(){
  console.log('byoo saying JS!');
}

byoo.sayJS();

이 코드는 경우에 따라 다르게 수행됩니다.

먼저, 첫번째 경우를 살펴 보겠습니다.

Object.defineProperty(Cadet.prototpye, "sayJS", {
                      writable: false
  					  ...
})

다음과 같이 Cadet 프로토타입 객체에 추가된 sayJS함수가 읽기 전용이라면, 엄격모드에서는 에러가 발생되지만 비 엄격 모드에서는 아무런 일도 일어나지 않게 됩니다.

일반적인 경우, sayJS함수가 읽기 전용이 아니라면

Object.defineProperty(Cadet.prototpye, "sayJS", {
                      writable: true
  					  ...
})

byoo객체에 sayJS함수가 속성으로 추가 되게 됩니다. 따라서 byoo객체에서 기존에 프로토타입 체이닝을 통해 사용된 sayJS함수에 접근할 방법이 사라지게 됩니다.

이와 같이 이름이 같은 함수가 속성에 추가될 경우, 기존에 사용하던 함수를 사용할 수 없게되는 것을 가려짐이라고 합니다. Javascript에서는 이처럼 Method overriding은 존재하지 않지만 가려짐을 사용해서 유사하게 동작되게 됩니다.

profile
유병우

0개의 댓글

관련 채용 정보