[JavaScript] 자바스크립트 프로토타입 이해하기([[Prototype]], __proto__)

Noma·2021년 9월 15일
0

오승환님, MJ Kim님 글을 바탕으로 정리하였습니다.

1. Prototype vs. Class

클래스 기반 객체지향 언어(Java, Python 등)와 달리 자바스크립트는 클래스 개념이 없는 객체지향 언어이다. 클래스가 없으니 기본적으로 상속기능도 없다. 그래서 자바스크립트는 보통 프로토타입(Prototype)을 기반으로 상속을 흉내내도록 구현해 사용한다.

ES6에 Class 문법이 추가되었지만 Class는 사실 함수이고 기존의 prototype 기반 패턴의 Syntactic sugar에 불과해, 여전히 클래스 기반이 아닌 프로토타입 기반이다.

2. 프로토타입, 어디에 쓰이는가?

자바스크립트에 클래스는 없지만 함수(function)와 new를 통해 클래스를 비스무리하게 흉내낼 수 있다.

function Person(){
    this.eyes=2;
    this.nose=1;
}
const kim=new Person();
const park=new Person();

console.log(kim.eyes); //2
console.log(kim.nose); //1
console.log(park.eyes); //2
console.log(park.nose); //1

위 코드를 보면 kimparkeyesnose를 공통적으로 가지고 있다. eyesnose는 두 개씩 총 4개가 메모리에 할당된다. 먄약 객체를 100개 만든다면 200개의 변수가 메모리에 할당될 거다.

이때 자바스크립트는 프로토타입을 기반으로 상속을 구현하여, 새로운 객체를 몇 개 만들든 상관없이 각 한 개의 noseeyes를 공용으로 사용할 수 있도록 만들어 불필요한 중복을 제거할 수 있다.

function Person(){}

Person.prototype.eyes=2;
Person.prototype.nose=1;

const kim=new Person();
const park=new Person();

console.log(kim.eyes); //2
console.log(kim.nose); //1
console.log(park.eyes); //2
console.log(park.nose); //1

간단하게 설명하면,

Person.prototype이라는 빈 Object가 어딘가에 존재하고, Person 클래스로부터 생성된 객체(kim, park)들은 어딘가에 존재하는 Object에 들어있는 값을 모두 갖다 쓸 수 있다.

즉, eyesnose를 어딘가에 있는 빈 공간에 넣어놓고 kimpark이 공유해서 사용하는 것이다. 어떻게 이런게 가능한 것일까?

지금부터는 프로토타입은 무엇인지, 왜 이렇게 쓰이는 지에 대해 좀 더 깊게 알아보자.

3. Prototype

ECMA-262에서 prototype은 'object that provides shared properties for other objects'로, 다른 객체에 공유 프로퍼티(메서드 포함)를 제공하는 객체를 말한다.

이는 Prototype ObjectPrototype Link로 이루어져 있다. 아래에서 하나씩 살펴보자.

3.1 Prototype Object

객체는 함수(function)로 생성된다.

function Person(){} 
const personObj=new Person(); 

personObj 객체는 Person이라는 함수로 생성된 객체이다. 이렇듯 객체는 언제나 함수에서 시작된다. 일반적인 객체 생성 방법도 예외는 아니다.

const obj={};

위 코드는 사실 다음과 같다.

const obj=new Object();

Object가 자바스크립트에서 기본적으로 제공하는 함수이다.

Object와 마찬가지로 Function, Array도 모두 함수로 정의되어 있다.

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

1. 해당 함수에 Constructor(생성자) 자격 부여
Constructor 자격이 부여되면 new를 통해 객체를 만들어 낼 수 있다. 이것이 함수만 new 키워드를 사용할 수 있는 이유다.

obj가 함수가 아니여서 constructor 자격이 없어 new를 사용할 수 없다.

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

생성된 함수는 prototype이라는 속성을 통해 Prototype Object에 접근할 수 있다. Prototype Object는 일반적인 객체와 같으며 기본적인 속성으로 constructor 함수와 [[Prototype]] 내부 슬롯을 가지고 있다.

constructor는 Prototype Object와 같이 생성되었던 함수를 가리키고 있다.
[[Prototype]]는 Prototype Link이다. 이는 밑에서 자세히 보도록 하자.

다시 kimpark이 나왔던 예제로 돌아가보자.

class Person{}
Person.prototype.eyes = 2;
Person.prototype.nose = 1;

var kim  = new Person();
var park = new Person():

console.log(kim.eyes); // => 2
...

이제 왜 Person.prototype을 사용하는지 보일 거다.
Prototype Object는 일반적인 객체이므로 속성을 마음대로 추가/삭제 할 수 있다. kimparkPerson 함수를 통해 생성되었으니 Person.prototype을 참조할 수 있게 된다.


kim에는 eyes라는 속성이 없는데도 kim.eyes를 실행하면 2라는 값을 참조하는 것을 볼 수 있다. 위에서 언급했듯이 Prototype Object에 존재하는 eyes 속성을 참조한 것인데, 이는 어떻게 가능한 걸까?

바로 kim이 가지고 있는 딱 하나의 속성 [[Prototype]]이 그것을 가능하게 해주는 열쇠이다.

prototype 속성은 함수만 가지고 있던 것과 달리,
[[Prototype]]이라는 내부 슬롯(자바스크립트 엔진의 내부 로직)은 모든 객체가 가지고 있다. 이는 상속을 구현하는 프로토타입 객체를 가리키고 있다.

즉,[[Prototype]]은 객체가 생성될 때 조상이었던 함수의 Prototype Object를 가리킨다. 위 예제에서 kim 객체는 Person 함수로부터 생성되었으니 Person 함수의 Prototype Object를 가리키고 있다.

하지만 [[Prototype]] 내부 슬롯에는 직접 접근이 불가능하다. 이는 프로토타입 체인의 단방향을 지키기 위해서이다. 만약 직접 접근이 가능하다면, 서로가 서로의 프로토타입이 되면서 프로토타입 체인이 무한으로 돈다.

따라서 __proto__ 프로퍼티로만 접근할 수 있다.

3.3 __proto__

모든 객체는 __proto__를 통해 자신의 프로토타입 ([[Prototype]]내부 슬롯)에 접근할 수 있다.

kim 객체의 __proto__를 까보니 역시 Person 함수의 Prototype Object를 가리키고 있다.

△ 객체, 함수, Prototype Object의 관계
위에서 kim.eyes를 했을 때 2가 나왔던 이유는, kim 객체가 eyes를 직접 가지고 있지 않아 eyes 속성을 찾을 때까지 상위 프로토타입을 탐색했기 때문이다.

3.4 프로토타입 체인

이렇게 __proto__ 속성으로 상위 프로토타입과 연결되어 있는 형태를 프로토타입 체인(Chain)이라고 한다.

만약 최상위인 Object의 Prototype Object까지 도달했는데도 못찾았을 경우 undefined를 리턴한다.

△ 프로토타입 체인, 최상위는 Object

이런 프로토타입 체인 구조때문에 모든 객체는 Object의 자식이라고 불리고, Object Prototype Object에 있는 모든 속성을 사용할 수 있다.

한 가지 예를 들면 toString 함수가 있다.

Object 속성인 toString 함수를 kim도 사용 가능

profile
Frontend Web/App Engineer

0개의 댓글