관리 메뉴

Jerry

PROTOTYPE (프로토타입)이란?? 본문

Front/JavaScript

PROTOTYPE (프로토타입)이란??

juicyjerry 2021. 4. 23. 14:21
반응형
이 글은 인프런, Javascript 핵심 개념 알아보기 - JS Flow, 정재남 강의를 수강하면서 정리한 내용입니다.

 

오늘 프로토타입에 대해서 알아보자!

오늘도 프로토타입을 정복하러 가보자!

💪😎💪😎💪😎💪😎💪😎💪😎💪😎

 

출처: pixabay.com/images/id -60527/

 

 

 

 


 

 

 

0. prototype, constuctor, __propto__

이번 프로토타입 정리 글에서 제목에 보이는 키워드가 자주 등장하니 인사 정도 해봐도 좋을 것 같습니다.

 

먼저!

아래 이미지가 무엇이냐면... 

 

 

 

 

바로 이것입니다!

 

 

 

 

우리가 평소에 사용하는 생성자 함수를 가지고 설명을 시작해보겠습니다.

생성자 함수를 new 연산자를 통해서 인스턴스를 만들었을 때, 그 인스턴스에는 constructor의 prototype이라고 하는 프로퍼티의 내용이 __proto__ 프로퍼티로 참조를 전달하게 됩니다.

 

 

무슨 말이냐면, constructor.prototype이랑 instance.__proto__랑 같은 객체를 바라본다는 의미입니다.

 

 

__proto__라는 프로퍼티는 생략이 가능해서 생략해 놓고 보면,

아래처럼 되고 실제로는 이렇지 않은데 이렇게 동작하는 것처럼 보입니다. (__proto__는 살아있음)

 

 

 

 

즉, 인스턴스에서 constructor에 있는 prototype 안에 들어있는 메서드들을 자신의 것처럼 접근하여 사용할 수 있다는 겁니다.

 

 

그래서 이 그림을 보고 처음에 소개한 키워드를 아래 그림을 보고 떠올려볼 수 있습니다.

 

 

 

 

배열에 대해서 먼저 살펴보겠습니다!

리터럴로 생성했든 new 연산자(new Array)로 생성했든, 이 배열은 Array라는 생성자 함수에 의해서 new 연산자를 사용해 만든 array의 인스턴스와 동일합니다. 그냥 그렇다고 합니다. ㅎㅎ

 

 

 

 

생성자 함수 Array에 의해서 new Array로 만든 배열 인스턴스, 이 사이에는.. 

Array에는 프로토타입이라는 프로퍼티 이외에도 여러 프로퍼티가 존재합니다. 

console.log로 Array를 찍어보면 아래와 같이 나옵니다.

 

 

 

 

실제로 찍어본 결과입니다.

 

 

 

 

이 prototype이라는 키워드가 인스턴스의  __proto__와 동일하게 연결이 된다는 겁니다!

 

 

 

 

그런데, prototype에는 무엇이 있냐 하면? 

우리가 배열을 다룰 때 사용하는 메서드가 다 여기 있습니다!

그러니깐 우리가 평소에 사용하는 배열 메서드가 다 Array 안에 prototype에 들어있는 겁니다.

그런데  __proto__를 생략할 수 있으니깐 우리는 평소에 배열(명)과 메서드 만으로 가져와 사용할 수 있다는 겁니다.

이것이 프로토타입의 본질입니다.

 

 

 

 

배열 리터럴로 출력해보겠습니다.

배열 1, 2, 3을 출력했더니, __proto__가 보입니다.

 

 

 

 

실제로도 그렇습니다!

 

 

 

 

__proto__를 눌러 열어보면, 위에서 보았던 prototype안에 있던 메서드를 확인할 수 있습니다.

 

 __proto__ 는 Array의 prototype를 가리킨다고 했는데 거기에 constructor가 있습니다. 

constructor에는 다시 Array 함수가 담겨 있습니다. 

 

 

 

 

배열에는 constructor라는 프로퍼티가 없는데 constructor 했더니 값이 출력했습니다. 

배열.constructor와 배열.__proto__.constructor를 출력했더니 같은 값이 출력됩니다.

즉, __proto__는 생략이 가능하므로, 전자는 생략한 방식으로 출력한 겁니다.

 

 

 

 

실제로도 그렇습니다.

 

 

 

다음 예시를 보겠습니다.

아래 코드에서 gomuClone1, 2, 3, 4는 전부 Person의 인스턴스가 됩니다. 

new Person은 생성자 함수로 만든 인스턴스입니다.

 

gomuClone1과 gomuClone2는 위에서 설명을 했으니 gomuClone3부터 보게 되면..

 

gomuClone3은 gomuProto에서 getPrototypeOf는 프로토타입을 반환하는 메서드를 이용해, gomuClone3에서 반환한 결과를 가지고 constructor에 접근하는 겁니다.

 

 

gomuClone4는 인스턴스가 아닌 생성자의 prototype에서 안에 있는 생성자에 접근하여 인스턴스를 생성합니다.

 

 

 

 

실제로도 그렇습니다!

 

 

 

 

그러니깐, 아래 방식으로 모두 생성자 함수의 프로토타입에 접근이 가능한 겁니다!

 

 

 

 

또, 위 코드에 constructor를 붙이면 다시 생성자 함수로 접근할 수 있는 법이 생기는 겁니다.

 

 

 

 

그래서 지금까지 이야기한 내용을 토대로, 아래와 같은 그림을 표현할 수 있는 것입니다.

하지만, 위에서 아래로 내려갈 수 있지만(new Array), 아래에서 위로는 올라갈 수 없습니다.

 

 

 

 

1. 메서드 상속 및 동작 원리

 

아래와 같은 코드가 있다고 할 때,

 

 

 

 

반복을 줄이고 싶어서 아래와 같이 했다고 해보면...

 

 

 

 

그렇게 되면

Person의 인스턴스는  setOlder와 getAge를 사용할 수 있습니다.

gomu나 iu는 자신의 것처럼 불러와 사용할 수 있습니다. 

또한, __proto__를 생략해서 호출할 수 있습니다.

 

 

 

 

여기서!

만약, 생략한 상태에서 this는 gomu를 가리킵니다.

생략하지 않은 상태에서 this는 gomu.__proto__가리킵니다. 

 

 

그래서, gomu.__proto__.setOlder()를 하고 gomu.__proto__getAge()를 하게 되면

NaN이 나오게 됩니다. 

 

 

무슨 말이냐 하면은,

this가 Person.prototype를 가리키니깐

Person.prototype에는 setOlder를 하려는 this.age, prototype의 age가 없습니다.

그래서 값이 undefine으로 나오게 되며 undefined + 1이 되니깐 NaN가 출력이 되는 겁니다.

 

 

 

그런데, 아래처럼 생략해 놓고 실행하면..

this가 gomu를 가리키니깐 gomu의 30이 +1 해서 31이 됩니다. 

 

 

 

 

만약, gomu.__proto__.age = 100; 를 추가해줬으면 원하는 결과대로 나오게 될 것입니다.

 

 

 

 

여기까지가 프로토타입의 정수라고 합니다.. ㅎ

 

 

2. 프로토타입 체이닝

Array.prototype은 객체입니다. 

프로토타입이라는 프로퍼티가 객체로 들어가 있습니다.

 

 

 

 

그럼 이 프로토타입의 프로퍼티가 객체인데, 이 객체를 무엇으로 만들까요?

생성자 Object를 new 연산자를 이용해 만든 객체입니다.

그러니깐 Object.prototype를 또 상속받을 수 있습니다.

 

예를 들어, 배열([1,2,3])에서 valueOf라는 메서드를 사용하게 되면,

배열에는 해당 메서드가 없으니깐 Array.prototype으로 올라갑니다. 여기서 있으면 여기에 있는 것을 사용하고

없다면, 궁극적으로 Object.prototype으로 올라가 해당 메서드를 사용합니다.

 

 

 

 

Object.prototype은 모든 데이터 타입에서 공통적으로 사용해야 할 메서드가 다 있는 겁니다.

자바스크립트에서 통용적, 범용적으로 사용해야 하는 메서드, 어떻게 보면, 기능적으로 약한 메서드들이 여기에 있습니다. 

 

 

 

 

그렇다면, 리터럴 객체는 어떨까요?

다른 데이터 타입과는 다르게 Object.prototype는 범용적으로 사용할 메서드가 들어 있으니

어쩔 수 없이 상속 구조상, 생성자 함수에 static 하게 메서드를 잔뜩 넣어놓았습니다.

 

 

 

 

아래 두 가지 예시를 살펴보겠습니다!

 먼저, toString 메서드는 MDN에서는 이렇게 정의되어 있습니다.

The toString() method returns a string representing the object.
The toString() 은 문자열을 반환하는 object의 대표적인 방법이다

 

 

왼쪽 이미지 예시부터 보면, 

첫 번째 출력은, arr 자체에 toString 메서드가 있으니깐 본인 걸로 실행이 됩니다.

두 번째 출력은, (최상위에) Object에 있는 toString 메서드로 실행이 됩니다.

세 번째 출력은, Object의 __proto__의 toString 메서드로 실행이 됩니다.

 

 

 

 

오른쪽 이미지 예시를 보면,

첫 번째 출력은, 왼쪽 예시의 첫 번째 출력과 동일 값이 나옵니다.

두 번째 출력은, Array.prototype.toString이 추가가 되어서, 배열에 값이 담겨 나옵니다.
세 번째 출력은, 여전히 Object의 __proto__의 toString 메서드로 실행이 되어 값이 나옵니다.

 

 

 

 

여기까지 강의 내용을 꼼꼼히 정리해보았습니다.

마지막으로, 아리 이미지를 보면서 전체적인 흐름을 상기시켜 보시고, 저처럼 직접 console에 찍어보면서 확인해보시면 이해가 더 잘 될 것 같습니다! (console.log 말고 console.dir로 구조를 보면서 확인하기!)

 

 

 

마지막으로, MDN에서 정의한 prototype으로 마치고 들어가겠습니다. 

이 글을 정리하고, "프로토타입이란 무엇일까?"라고 생각해봤는데 표현하기가 어려워서 아래 정의를 가져오게 되었습니다.

 

JavaScript는 흔히 프로토타입 기반 언어(prototype-based language)라 불립니다.— 모든 객체들이 메소드와 속성들을 상속받기 위한 템플릿으로써 프로토타입 객체(prototype object)를 가진다는 의미입니다. 프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메서드와 속성을 상속받을 수도 있고 그 상위 프로토타입 객체도 마찬가지입니다. 이를 프로토타입 체인(prototype chain)이라 부르며 다른 객체에 정의된 메소드와 속성을 한 객체에서 사용할 수 있도록 하는 근간입니다.
정확히 말하자면 상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의 prototype이라는 속성에 정의되어 있습니다. JavaScript에서는 객체 인스턴스와 프로토타입 간에 연결(많은 브라우저들이 생성자의 prototype 속성에서 파생된 __proto__ 속성으로 객체 인스턴스에 구현하고 있습니다.)이 구성되며 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메서드를 탐색합니다.

 

 

 

반응형