강호형(hohyeong kang)
강호형(hohyeong kang)

Categories

Tags

0. 서론

자바스크립트는 프로토타입 기반 언어라고 합니다. 이는 클래스와 인스턴스를 통해 객체를 정의하는 클래스 기반의 객체지향 언어와는 달리, 원형(프로토타입)이라는 개념을 통해 객체를 정의하고 객체간의 관계를 형성하고 있음을 의미합니다. 사실 ES2015부터 자바스크립트에서도 class 키워드를 지원하고 있는데요, 다른 클래스 기반 언어의 클래스 문법과 상당히 비슷해서 저처럼 2015년 이후에 자바스크립트를 접하고, 이미 클래스 문법에 익숙했던 사람에게는 더욱 프로토타입이라는 개념을 멀리하게 되는 원인이 되는 것 같습니다. 하지만 모든 것이 객체인 자바스크립트에서 그 객체의 기반이 되는 프로토타입을 모른다면 자바스크립트를 제대로 이해하고 있다고 할 수 없겠죠. 자바스크립트의 기본 컨샙과 객체, 상속에 대해 제대로 이해할 수 있도록 프로토타입에 대해 살펴보도록 하겠습니다.

목차

  1. 객체를 만드는 방법
  2. 상속과 프로토타입
  3. 자바스크립트에서 프로토타입 다루기

1. 객체를 만드는 방법

본격적으로 프로토타입에 대해 다루기 앞서, 자바스크립트의 객체 생성법에 대해서 간단하게 살펴보겠습니다. 자바스크립트에서 객체를 만드는 방법은 크게 두 가지가 있습니다.

let hohyeong = {};		//1. 객체 리터럴
let hohyeong = new Person();	//2. 객체 생성자

객체 리터럴 방식은 중괄호 내에 직접 키, 값 쌍으로 구성된 프로퍼티를 작성하는 방식으로 쉽게 객체를 생성할 수 있습니만 유사한 객체를 여러 번 만들 경우에는 적합하지 않습니다. 이때 우리는 객체 생성자 방식을 통해서 미리 정의해놓은 프로퍼티를 공통적으로 가지는 객체들을 손쉽게 만들어 냅니다. 객체 생성자 방식을 구현하는 방법은 생성자 함수 또는 클래스를 이용하는 것입니다(사실 자바스크립트에서는 클래스도 함수다).

생성자 함수

생성자 함수와 일반 함수에 기술적인 차이는 없지만 어떤 함수를 생성자 함수로 사용하기 위해선 반드시 new 연산자를 앞에 붙여서 실행하여야 합니다. new 연산자를 통해 함수를 실행하면 다음과 같은 알고리즘으로 객체를 생성하게 됩니다.

  1. 빈 객체를 만들어 this 에 할당
  2. 함수 본문 실행. 이때 this에 여러 프로퍼티 추가 가능.
  3. this 반환

이때 3번 this 반환은 암시적으로 실행되기 때문에 굳이 생성자 함수에서 return 문을 명시적으로 써줄 필요가 없습니다. 만약 return 문이 있고, 객체를 반환하고 있다면 this 는 무시되고 해당 객체가 반환됩니다(원시형을 반환한다면 return문 무시).

2. 상속과 프로토타입

위에서 객체를 생성하는 법, 그리고 동일한 성격의 객체를 쉽게 찍어내는 법에 대해 알아보았습니다. 여기에서 더 나아가 기존의 객체가 가진 기능을 포함하면서 몇가지 기능과 속성을 추가하여 확장된 객체를 만들고 싶다면 어떻게 해야 할까요? 이러한 상황을 상속이라고 하며 객체 지향 프로그래밍에서 객체들 간의 관계를 구축하는 방법입니다. 클래스로 객체가 정의되어 부모 클래스와 자식 클래스로 구성되는 고전 상속과는 달리, 자바스크립트에서는 프로토타입을 기반으로 차등 상속을 가능하게 합니다. 차등 상속(Differential inheritance)이란 대부분의 객체는 다른 객체로부터 파생되며 몇몇 일부 측면에서만 다르다라는 원칙을 바탕으로 합니다[wiki]. 원형(프로토타입)을 두고, 그와의 차이점을 기술하는 것으로 객체간의 관계를 설정하는 것입니다. 따라서 차등 상속은 상속 받는 객체가 상속 하는 객체에 대한 포인터를 가지는 방식으로 구현됩니다. 또한 특정 클래스로 생성하는 모든 인스턴스의 속성을 클래스에서 미리 정의하는 것과 달리 프로토타입은 특정 객체가 가져야 하는 초기 속성만을 명시하므로 후에 동적으로 속성을 추가하거나 삭제할 수 있습니다. 한마디로 차등 상속은 객체가 클래스를 따로 정의할 필요 없이 다른 객체로부터 직접 정의될 수 있음을 의미합니다.

자바스크립트에서는 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체를 가지고 있습니다. 정확히는 모든 객체가 [[Prototype]] 이라는 숨김 프로퍼티를 통해 자신의 프로토타입이 되는 다른 객체를 참조하고 있습니다. 참조되는 프로토타입 또한 자신의 프로토타입을 참조하고 있으며, 이러한 반복은 null 을 프로토타입으로 가지는 객체에서 끝나게 됩니다. 이를 프로토타입 체인 이라고 하며, 객체의 특정 프로퍼티에 접근할 때 자바스크립트는 해당 프로퍼티가 등장할 때까지 이러한 프로토타입 체인을 순차적으로 탐색하게 됩니다.

3. 자바스크립트에서 프로토타입 다루기

위에서 프로토타입 기반 객체지향에서 상속이 어떻게 구현되는지 살펴보았습니다. 이제 본격적으로 자바스크립트에서 프로토타입을 어떻게 다루는지 살펴보겠습니다. 특히 이 파트에서 등장하는 세 가지 프로퍼티 prototype, [[Prototype]], __proto__ 가 모두 다른 개념이라는 것을 유념하면서 읽어보시기 바랍니다.

객체의 [[Protytype]]

위에서 언급한 바와 같이 자바스크립트의 객체는 명세서에서 명명한 [[Prototype]] 이라는 숨김 프로퍼티를 가지며 이는 자신의 프로토타입이 되는 객체를 참조하고 있습니다. 이는 숨김 프로퍼티이기 때문에 이를 통해 객체의 프로토타입에 접근하거나 변경할 수 없습니다(__proto__ 프로퍼티가 등장한 이유). [[Prototype]] 프로퍼티는 프로토타입 체인을 형성하여 자바스크립트에서 상속을 구현하는 핵심적인 요소입니다.

생성자 함수의 prototype 프로퍼티

prototype 은 해당 생성자 함수로 생성된 객체의 [[Prototype]] 을 설정하기 위한 생성자 함수의 일반 프로퍼티 입니다. [[Prototype]] 은 객체의 프로퍼티, prototype은 함수의 프로퍼티라는 차이에 유의하세요.

예를 들어 Person 이라는 생성자 함수를 만들고 Person.prototype = animal 과 같이 prototype 프로퍼티에 animal 객체를 할당한다면 new Person() 을 호출해 만든 모든 객체의 [[Prototype]] 프로퍼티는 animal 객체를 가리키게 됩니다. 즉, prototype 프로퍼티는 생성자 함수를 통해 생성하는 객체들의 프로토타입을 설정하기 위해 사용하게 됩니다.

개발자가 특별히 할당하지 않더라도 기본적으로 모든 함수는 prototype 프로퍼티를 가지고 있습니다. 이 경우에 prototype 프로퍼티는 constructor 프로퍼티만을 가지고 있는 객체를 가리키고 있으며, constructor 프로퍼티는 함수 자신을 가리킵니다. 따라서 객체는 자신의 프로토타입을 통해 자신의 생성자 함수를 획득할 수 있게 됩니다. 주의해야 할 점은 constructor 는 단순히 프로퍼티이기 때문에 개발자에 의해 언제든지 변경될 수 있다는 점입니다. 따라서 의도한 바가 아니라면 prototype 자체를 새로 할당해서 constructor 프로퍼티를 잃어버리지 않도록 해야합니다(또는 직접 다시 constuctor 프로퍼티를 할당할 수도 있다).

prototype

생성자 함수와 객체, 프로토타입의 관계

__proto__ : [[Prototype]]의 getter, setter

__proto__ 는 다시 개별 객체의 프로퍼티 입니다(정확히는 Object.prototype의 접근자 프로퍼티). 이전에는 특정 객체의 프로토타입을 얻거나 설정하는 것이 불가능했기 때문에 브라우저에서 표준 접근자인 __proto__ 를 통해 이를 가능하게 하였습니다. ES2015에서 Object.setPrototypeOfObject.getPrototypeOf 가 표준이 되어 이를 통해 동일한 기능을 수행할 수 있으며 단순히 객체의 키 값인 __proto__를 사용하는 것이 경우에 따라 문제가 될 수 있으므로 프로토타입을 다루고자 한다면 그에 맞는 프로토타입 메서드를 사용하는 것을 우선적으로 합니다. 다음은 프로토타입을 다루는 대표적인 메서드들입니다.

  • Object.create(proto, [descriptors]) : [[Prototype]]이 proto를 참조하는 빈 객체를 생성한다.
  • Object.getPrototypeOf(obj) : obj의 [[Prototype]]을 반환한다.
  • Object.setPrototypeOf(obj, proto) : obj의 [[Prototype]]이 proto가 되도록 설정한다.

내장 프로토타입

자바스크립트의 모든 내장 생성자 함수 또한 prototype 프로퍼티를 사용합니다. Object Array Date Function 과 같은 자바스크립트 내장 객체들은 프로토타입에 내장 메서드를 저장해둡니다. 이것이 우리가 따로 설정하지 않아도 여러 내장 매서드를 사용할 수 있는 이유입니다. 내장 객체를 생성하면 그 객체의 프로토타입에 매서드들이 정의되어 있기 때문이죠.

예를들어 배열 [a,b,c] 를 만들면 내부적으로 new Array() 생성자가 호출되며 이때 Array.prototype이 해당 배열의 프로토타입이 되기 때문에 Array.prototype.push() 와 같은 내장 매서드를 마음껏 쓸 수 있게 되는 것입니다.

특히 자바스크립트 명세서에는 모든 내장 프로토타입의 꼭대기엔 Object.prototype이 있어야 한다 라고 규정하고 있습니다. 이것이 종종 “자바스크립트에서 모든 것은 객체다” 라고 하는 이유입니다.

image

내장 객체간의 상속(https://ko.javascript.info/native-prototypes)

4. 마치며

사용되는 단어들이 서로 너무 비슷하다는 점이 자바스크립트의 프로토타입을 이해하는데 가장 어려운 점이 아닐까 싶습니다. 이 글에서는 자바스크립트에서 프로토타입이 어떻게 구현되고 있는지만 다뤘지만 이를 제대로 활용하여 객체지향 프로그래밍을 하는 것은 또 다른 차원인 것 같습니다. 클래스에서는 할 수 없는 프로토타입만의 장점도 있기 때문에 조만간 프로토타입 기반의 객체 지향을 제대로 활용하는 방법에 대해서도 학습하려고 합니다. 그래도 프로토타입에 대해 학습하면서 자바스크립트가 어떻게 동작하는지 그 핵심을 들여다 볼 수 있었던 것 같아서 재미있었습니다.


참고문서

https://ko.javascript.info/prototypes

https://ko.javascript.info/object

https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Object_prototypes

https://developer.mozilla.org/ko/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Details_of_the_Object_Model

위키-프로토타입-기반-프로그래밍