Prototype Chain - JS의 구조를 설명하다

조 은길·2021년 11월 10일
0

Javascript 정리

목록 보기
23/48
post-thumbnail

Blogging - Prototype Chain

Bare mininum requirements

  • __proto__, constructor, prototype 이 각각 어떤 관계를 가지고 있는지 조사해봅니다.

들어가기

  • 만약, 당신이 개발자로써 철저하게 시간 낭비라고 생각하는 일이 있다면, 어떤 것이겠는가??

    => 아마, 자신의 코드를 일일히 다시 쳐야 되는 것일 것이다.

이게 무슨 소리인고 하면, 다른 곳에서 이미 정의한 함수나 메소드를 갖다쓰면 되는데, 그 함수가 필요하다고 일일히 새로운 객체나 함수에 기존의 것을 새로 넣어줘야 되는 것은 시간 낭비라는 생각이 든다.

바로, 이런 번거로움을 해결하고자 나온 개념이 상속(Inheritance)이다.


상속(Inheritance)

이 상속을 통해서, 필요한 객체를 상속 받아 해당 객체의 property와 method들을 사용할 수 있다.

사실, 이 상속이라는 개념은 객체지향형 (Object-oriented programming) 언어의 특징 중에 하나이다.

다시 말해서, 상속을 사용하는 JS도 객체지향형 언어이다. 그러나, 이 상속을 이뤄나는 방법은 JAVA나 C++같은 다른 객체 지향형 언어와는 다르다.

JS를 제외한 객체지향형 언어들은 class를 사용하여 상속한다. 그러나, JS는 Prototype를 이용하는 방법으로 상속을 한다. 아 물론, 최근 ECMA6 표준에서는 Class 문법이 추가되었다. 하지만 문법이 추가되었다는 것이지, 자바스크립트가 클래스 기반으로 바뀌었다는 것은 아니다. 단지, class를 다른 언어들에서도 쓰니, class 비슷한 것을 만들어 준거다.

그래서, 교육 엔지니어님 중 한 분은 JS는 객체지향형 언어가 아니라, 객체지향형 언어가 유행하니까 그것을 흉내내는 언어라고도 하신다.

상속에 관한 자세한 내용은 필자가 쓴 Inheritance pattern in JS를 확인하기를 바란다.


Prototype

JS에서 원시 타입을 제외한 모든 것들은 객체로 이뤄져있다. 그리고 이 객체들은 모두 특정한 properties 와 methods를 가진 prototype을 상속받는다.
결국, JS는 Prototype으로 시작해서 Prototype으로 끝난다고 해도 과언이 아니다.

프로토타입

  • arr 이라는 빈배열은 Array라는 prototype을 상속 받는다. 여기는 배열에 사용되는 사용한 method들과 property들이 있다.
  • 그리고 Array 객체는 Object를 상속 받는다.
  • 모든 objects들은 특별하게 숨겨진 property를 가지고 있는데, 바로 [[prototype]]이다.
    • [[ 는 숨겨졌다는 의미이다.
    • [[prototype]]__proto__는 같은 의미이다.
    • arr의 prototype을 확인해보고 싶다면, arr.__proto__를 타입해보면 된다.
    • 이러한, prototype들은 JS engine이 기본값으로써 설정해둔 것이다. 덕분에, 우리는 prototype들에 내장되있는 properties와 methods을 특별한 조치없이 이용할 수 있는 것이다.
  • 본격적으로 들어가기 앞서, 간단한 예시를 하나 보자
// food object
let food = {
  init: function(type) {
    this.type = type;
  },
  recipe: function(){
    console.log(`This is a recipe for ${this.type}.`)
  }
}

//food.init('Donuts');

//food.recipe();

// 만약 그냥 도넛만이 아니라, 여러 개 다른 종류의 food를 만들고 싶다면 어떻게 해야할까??

// Object.create()
// => You will recall that Object.create() enables us to define a new object 
// ( in our case a donut or waffle ) by also defining its prototype.
// 이 방법의 강력함은 food 객체 하나를 가지고 여러 개의 다른 속성의 food를 만들 수 있다는 것이다. 
// 핫도그, 피자 등등

let donut = Object.create(food); 
let orange = Object.create(food);


// donut.init('Donuts');
// donut.recipe();
// orange.init('Orange');
// orange.recipe();

// Q. object.create를 사용할 때, 이것은 객체마다 고유한 food의 복사본을 갖는가?? 
// 아니면, 그저 the original food object를 참조 하는가??

food.recipe = function() {
  console.log(`this method has beed revised - ${this.type}` );
}
// 이게 찍힌다면, reference라는 의미겠죠!!

donut.init('Donuts');

// what if??
food.type = "I change the food type!! Hah!! Hah!!";
// donut은 donut의 property를 먼저 보기 때문에, donut 자체적으로 해당 property가 존재한다면, 
// food까지 가서 찾아보지 않기 때문에, 바뀌지 않는다.


orange.init('Orange');

donut.recipe(); // 'this method has beed revised - Donuts'
orange.recipe(); // 'this method has beed revised - Orange'

// food object is only a fallback for donut and orange. 
// 만약 도넛과 오렌지에 해당 property가 없다면, 그 다음에 food에서 해당 property를 찾아본다.
// 결론은 donut과 orange는 그들 안에서 해당 property를 찾을 수 없을 때,
// food Object로 올라서 찾게 된다.

// ///////////////////////////////////////////

// food가 donut의 prototype임을 증명하는 방법
// isPrototypeOf();

console.log(food.isPrototypeOf(donut)); // => true


자바스크립트에는 Prototype Link 와 Prototype Object라는 것이 존재한다. 그리고 이 둘을 통틀어 Prototype이라고 부른다.

Prototype Object (프로토타입 객체)

MDN 내린 정의는 다음과 같다.
"자바스크립트에서 객체가 생성될때, 생성된 객체의 부모가 되는 객체의 원형을 프로토타입 객체(Prototype Object)라고 한다."

ex)

a라는 함수의 prototype은 부모가 되는 객체의 원형인 Object를 기본값으로 품고있다.

하지만, 이 말만 들어서는 뭔 말인지 도무지 모르겠으니, 다른 방식으로 접근해보자!!

이 부분은 오승환님의 글과 Udemy 강의에서 배운 내용을 바탕으로 작성한다.

객체는 언제나 함수(Function)로 생성된다.

function Person() {} // => 함수

let personObject = new Person(); // => 함수로 객체를 생성
// new 키워드는 빈 객체를 생성한다.
// 그 다음, Person()이라는 함수를 constructor 함수로 설정한다.

즉, personObject은 Person이라는 함수를 기본으로 만들어진 객체이다. 이렇듯, JS의 객체들은 함수를 기본으로 만들어진다.

// 형태는 다르지만, 똑같은 빈 객체 선언 방식
let obj = {}; // 1
let obj = new Object(); // 2

// 형태는 다르지만, 똑같은 빈 배열 선언 방식
let arr1 = []; // 1
let arr1 = new Array(); // 2
  • obj는 Object()라는 함수를 바탕으로 만들어진 빈 객체이다. 즉, obj는 Object()가 포함하고 있는 모든 property와 method들을 사용할 수 있게 된다.

  • arr 역시 Array()라는 함수를 바탕으로 만들어진 빈 배열이다. 즉, arr은 Array()와 Array()가 상속하고 있는 ( 프로토타입 체인으로 연결된 ) 모든 property와 method들을 사용할 수 있게 된다.
    ( FYI 배열도 객체이다. )

  • 그렇다면 이것이 Prototype Object랑 무슨 상관이있느냐?

    Prototype Object는 함수가 생성될 때만이 자동적으로 생성된다. 즉, 오직 함수만이 사용할 수 있다!! 객체나 배열 등 다른 타입들이 선언될 때에는, Prototype Object가 생성되지 않는다.
    즉, 객체나 다른 형태에서는 애초에 .prototype으로 접근이 불가능하다.

지금까지 내용들을 정리해보면 다음과 같다.

함수가 정의될 때는 2가지 일이 동시에 이루어진다.

1. 해당 함수에 Constructor(생성자) 자격 부여

Constructor 자격이 부여되면 new를 통해 객체를 만들어 낼 수 있게 됩니다. 이것이 함수만 new 키워드를 사용할 수 있는 이유입니다.

=> 이게 무슨 말일까?? 위의 예시에 봤듯이 JS의 모든 객체는 함수를 생성자로 설정하면서 만들어진다.

생성자는 객체를 생성하기 위해서 필요하다. 그리고 그 생성자는 오직 함수 형태로써만 존재할 수 있다. 즉, 1번의 말은 특정 함수가 만들어지는 순간 그 함수는 JS 객체를 만들어내기 위한 생성자 함수가 될 수 있다는 말이다.

2.해당 함수의 Prototype Object 생성 및 연결
함수를 정의하면 함수만 생성되는 것이 아니라 Prototype Object도 같이 생성이 된다.
=> 즉, 오직 함수만이 Prototype Object를 갖는다.


그리고 생성된 함수는 prototype이라는 속성을 통해 Prototype Object에 접근할 수 있습니다. Prototype Object는 일반적인 객체와 같으며 기본적인 속성으로 constructor__proto__를 가지고 있다.

constructor는 Prototype Object와 같이 생성되었던 함수를 가리키고 있다.
__proto__는 Prototype Link이다. 밑에서 자세히 설명합니다.

  • 이제 예시를 통해서 좀 더 깊게 이해해보자

    Q. kim은 Person()을 통해서 만들어진 객체이다. 그리고 Person에 eyes는 prototype을 이용해서 속성을 추가해주었고, nose는 그냥 추가해주었다. 그런데, 왜 Person()을 이용해서 만들어진 kim에는 eyes라는 속성만 있고, nose라는 속성은 없을까??
    => 바로, 오직 prototype 키워드를 이용해서만이 person()의 constructor 접근할 수 있기 때문이다. 바로 위에서 Person.prototype에 constructor로써 자기 자신을 품고 있는 것을 알수 있다. 그리고 모든 객체들은 바로 이 constructor를 통해서 만들어지기 때문에, 아무런 변화도 일어나지 않은 것이다.
    => 다시 말해서, eyes는 이미 만들어진 객체뿐 아니라, 앞으로 Person()을 통해서 만들어지는 객체들 역시도 이 값을 포함하고 있다.
    => 그러나, 지금 Person.nose를 찍어본다면, 여전히 값을 잘 나온다. 하지만, 이 값은 Person이라는 새로운 변수가 생성되고 그 안에 값으로 nose라는 key값이 들어가 있는 것이지, Person()와는 아무런 관련도 없다. ( 사실, 나는 이 부분 때문에 많이 헷갈렸었다. )

그래서, 대체 Prototype Object 어디다 쓰는 걸까??
Udemy 강의에서 가져온 예시를 통해 확인해보자!!
이제 carrot.yummy; => true 가 찍히는 것을 확인할 수 있다.
다시 말해서, Carrot 함수의 prototype으로 food라는 객체를 지정해놓으므로써, prototype chain에서 food가 Carrot의 부모가 됐고, 그 덕분에 Carrot은 food의 method와 property를 사용할 수 있게 됐다.
- Q. 그렇다면, 이 상황에서 carrot.yummy를 false로 바꾸면, food의 yummy로 바뀔까??
food는 바뀌지 않는다. 이유는 food 안에 yummy가 있기 때문에, 구지 다른 곳까지 가서 yummy를 찾지 않는다. 그래서 food.yummy는 변하지 않았다. 오직 carrot.yummy만이 false로 바뀐다.

Q. food.yummy를 바꾸면, carrot.yummy도 똑같이 바뀔까??
carrot은 바뀐다. 이유는 애초에 carrot은 yummy라는 property가 없다. 이것은 food로부터 상속받은 거다. 즉, carrot은 yummy를 찾으러 food까지 올라가야 하고, 그 자리에는 변경된 yummy값이 있는 원리이다.

이 원리를 정리해보면 다음과 같다.


  • Prototype Object가 함수만이 가지는 속성이라면, Prototype Link는 함수를 포함한 모든 JS 객체들이 가지는 속성이다.
  • 생성된 객체안에서 부모 객체의 주소값을 가지고 있는 부분을 프로토타입 링크(Prototype Link)라고 부른다.


Object.setPrototypeOf(guestUser, user); 
// => guestUser가 user의 property를 사용할 수 있게 됐다.
// => 즉, guestUser의 Prototype( or 부모)가 user가 됐다.
  • guestUser에는 member라는 key가 없다. 그러나, __proto__를 통해 user의 속성인 member에 접근할 수 있게 된거다.
  • 그리고 user가 바뀌게 되면, guestUser 역시 변경된 값이 적용된다.

  • 이렇게 프로토타입 링크를 통해, 객체지향의 상속개념과 같이 부모객체의 속성, 메소드를 상속받아 객체를 재생성하며 프로그래밍하는 기법을 프로토타입 프로그래밍(Prototype-based programming)이라고 한다.
  • 그렇기 때문에 자바스크립트는 Prototype에 기반한 언어이다.
  • 그리고 이렇게 꼬리에 꼬리를 물고 연결 되는 Prototype을 Prototype chain이라고 한다.

Prototype chain 예시

ex1) Human이라는 클래스와 인스턴스, 그리고 프로토타입과의 관계

ex2) Array(배열) 클래스와 인스턴스, 그리고 프로토타입과의 관계

Prototype chain 한 눈에 보기

=> 여기서 짚고 넘어갈 만한 점은 모든 JS prototype들은 결국 Object로 끝나게 된다. 이 객체는 대문자 O이다!! 그리고 이 객체를 JS Object 라고도 한다. 모든 함수를 포함한 모든 JS 객체의 시작점이다.
=> JS Objectprototypenull이다.
=> 모든 JS 객체들은 prototype으로 또다른 objectnull을 갖는다!!

  • 좀 더 확실한 이해를 위해서, 빈 배열 a가 어디까지 prototype으로 올라갈 수 있는지 확인해보자!!

    이것을 도표로 정리해보면, 다음과 같다.

자료출처 및 참고자료

오늘 TIL은 코드스테이츠에서 학습한 내용과 오승환님의 블로그 그리고 Udemy에서 학습한 내용들을 바탕으로 작성됐다.

프로토타입 이해하기

JavaScript 객체 지향 프로그래밍 - 15. prototype vs proto

profile
좋은 길로만 가는 "조은길"입니다😁

0개의 댓글