prototype-중급자로 가기 위한 여정

YEONGHUN KO·2021년 11월 10일
1

JAVASCRIPT - BASICS

목록 보기
5/27
post-thumbnail

5. proto vs prototype

자바스크립트를 흔히 prototype 기반 언어라고 한다. 그 이유는 객체를 만들때 이미 그 객체가 만들어지는 시점에 자동으로 담겨있는 원형의 객체가 또 있기 때문. 그 원형의 객체는 코드상 그 객체안에 아무것도 없어도 어딘가 숨겨져있기 때문에 그 원형의 객체안에 있는 기능들을 사용할 수 있다. 걍 쉽게 말해 유전자라고 생각하면된다.

Array.prototype.cool = () => console.log('cool')

이렇게 해놓으면 이제 Array를 만드는 족족 유전자에 cool함수가 저장되어있기 때문에 cool함수를 굳이 추가하지 않아도 쓸 수 있게 된다.

이 관점에서 생각해볼 수 있는것!
바로, prototype으로 인한 상속을 통해 메모리를 획기적으로 아낄 수 있게 되었다. 이게 무슨 말이냐??

prototype에 추가된 속성은 이후 모든 복제될 자식 instance에 포함된다. 더 자세히 말하면 자식 instance는 prototype에 추가된 속성을 공통으로 사용할 수 있다. 코드로 보자

function Person(name,age,address){
  this.name = name;
  this.age = age;
  this.address = address;
}

var kim = new Person('kim',20,'seoul')


var lee = new Person('lee',32,'seoul')

라고 했을때 Person을 통해서 만든 복제인간들이 자기소개를 하게 하고 싶다면??

kim.introduction = function() {
  console.log(`hello my name is ${this.name} and I'm ${this.age} years old and I live in ${this.address}`)
}

lee.introduction = function() {
  console.log(`hello my name is ${this.name} and I'm ${this.age} years old and I live in ${this.address}`)
}

라고 해야한다. prototype같은게 없을 경우. 즉 모든 자식이 생성될때마다 익명함수가 메모리에 추가되는 것이다.

근데 모든 인스턴스에 한 번에 introduction을 추가하려면?? 상식적으로 생각해봐도 Person을 건드리면 될것이다.

Person함수안에다가 추가해도 되겠지만 prototype에다가 추가해도 된다.

Person.prototype.introduction = function() {
    console.log(`hello my name is ${this.name} and I'm ${this.age} years old and I live in ${this.address}`)
}

그러면 모든 instance들이 Person.introduction을 공유하게 되면서 메모리 사용을 획기적으로 절약할 수 있게 된것이다.

추후에 더 자세히 설명하겠지만 prototype은 객체에있는 멤버변수,함수를 다른 객체에 상속하기 위해 만들어졌다. 즉, prototype을 통해 객체지향코드를 짤 수 있는 것이다. class/extend키워드로 코드를 짜고 babel로 compile을 해보면, prototype을 통해서 상속하는 코드를 볼 수 있을 것이다. 어찌되었든 javascript는 결국엔 prototype을 통해 다른 객체에 상속을 한다.

그래서 자바스크립트의 class 키워드는 java에서 class같은 애들과는 조금 다르다. 왜냐면 prototype을 가공한 syntatic sugar로서 compile 되기 때문.

우선 설명하기에 앞서 생성자 함수를 2개 만들자

function Person(name, first, second) {
  this.name = name;
  this.first = first;
  this.second = second;
}

function PersonPlus(name, first, second, third) {
  Person(name, first, second);
  this.third = third;
}

var go = new PersonPlus("go", 50, 60, 90);

그리고 person을 personplus에 상속한다고 할때 위의 코드처럼하면 상속이 안된다. person이 아직 new를 통해서 객체화되지 않았기 때문에다. 그냥 일반 함수를 호출한것일뿐.

여기서 중요한것은 this를 어떻게 실현시킬것인가이다. 바로 call 함수를 사용하는것! "이미 객체인" PersonPlus 함수의 this를 person안에다가 호출하여 Person에 대입하는것이다.

function PersonPlus(name, first, second, third) {
  Person.call(this, name, first, second);
  this.third = third;
}

그리고 person생성자에 sum을, personplus 생성자에 avg를 함수를 추가하고 싶다고 하자. 또한 new를 통해서 Personplus 함수를 모체로 하는 kim이라는 새로운 객체를 만들고 싶다.

Person.prototype.sum = function () {
  return this.first + this.second;
};

PersonPlus.prototype.avg = function () {
  return (this.first + this.second + this.thrid) / 3;
};

var kim = new PersonPlus("kim", 10, 20, 30);

그리고 PersonPlus 생성자 함수에다가 person의 sum 함수를 상속하고 싶다. 여기서 복잡해진다. 먼저 각 객체안에 default로 담겨진 property가 있다. 바로 __proto__ 이다. 그 __proto__의 관계와 , 특히 proto와 prototype의 관계를 잘 이해해야한다. 근데..이게 무슨 개소리야 할것이다.

이고잉님이 정말 친절하게 그림으로 잘 설명해주셨다. 아래 그림을 살펴보자.

prototype{:class="img-fluid"}

우선 kim.avg()를 입력해보자. 그럼 컴퓨터는 우선 kim 안에서 avg()를 찾으려고 할 것이다. 없으면 kim안에 있는 __proto__ 함수에서 찾으려고 할 것이다. 거기도 없으면 오류가 발생한다. proto 링크의 방향은 이런식으로 형성된다. avg() 함수같은 경우 kim의 __proto__안에 있으므로 문제없다. 문제는 kim.sum() 을 호줄하려고 할때이다. __proto__에도 없으므로 오류가 발생한다.그럼 proto링크를 생각해봤을때 PersonPlus.prototype.__proto__ = Person.prototype 라는 로직을 짜면 문제가 없을것이다.

PersonPlus.prototype.__proto__ = Person.prototype;

//또는

PersonPlus.prototype.sum = Person.prototype.sum;

console.log(kim.sum());
// 110

근데 __proto__는 비공식적인 방법이고 좀더 안전하고 효율적인 Object.create()를 사용해주자.

PersonPlus.prototype = Object.create(Person.prototype);
console.log(kim.avg());
// avg() is not a function

근데 요렇게 바꾸면 personplus의 prototype이 person의 prototype으로 상속된다. 그러면 기존에 personplus.prototype에 추가한 avg() 함수가 덮어씌어지면서 avg() 함수가 없어진다. 따라서

PersonPlus.prototype.__proto__ = Object.create(Person.prototype);
console.log(kim.avg());
// 66.66666

//또는

PersonPlus.prototype = Object.create(Person.prototype);
PersonPlus.prototype.avg = function () {
  return (this.first + this.second + this.third) / 3;
};

console.log(kim.avg());
// PersonPlus.prototype를 교체한다음 avg()를 다시 추가하면 사용가능하다.

이렇게 해야 avg() 함수도 살아남으면서 person.prototype 안에 있는 sum 함수도 사용할 수 있게 된다.

P.S: 참고로 객체의 모체를 알고 싶다하면.(너의 정체, 원래 클래스가 뭐냐..?) 객체.constructor 라고 하면 된다. 다시 위의 그림을 보자. constructor 함수는 다시 모체 객체를 가리키고 있기 때문이다. 여기서 더 나아가 생각해보면

PersonPlus.prototype = Object.create(Person.prototype);

라고 한다면 PersonPlus.prototype은 Person.prototype 로 교체되었기 때문에 PersonPlus.prototype.constructor = Person 이 될것이다.

사실 함수객체끼리 상속하기 보다 클래스를 만들어서 super()를 사용해 상속하는게 훨씬 깔끔하고 쉽다. 근데 proto 링크를 알아두는 것은 자바스크립트 중급자로 가기위한 필수 관문이므로 자바스크립트를 주 언어로 한다면 꼭 알아두어야 할것이다.

참고자료: https://www.nextree.co.kr/p7323/

profile
'과연 이게 최선일까?' 끊임없이 생각하기

0개의 댓글