1. 프로토타입이란?
JavaScript에서는 객체를 상속하기 위하여 프로토타입(prototype)을 사용합니다.
때문에 JavaScript는 프로토타입 기반 언어(prototype-based language)라고 불립니다.
모든 객체들이 메서드와 속성들을 상속 받기 위한 템플릿으로 프로토타입 객체(prototype object) 를 가진다는 의미입니다.
프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메서드와 속성을 상속 받을 수 있고 그 상위 프로토타입 객체도 마찬가지입니다. 이를 프로토타입 체인(prototype chain)이라 부릅니다.
2. 프로토타입의 작동 원리
이제 코드를 작성하며 설명해 드리겠습니다.
// 생성자 함수
// (번외) 생성자 함수를 선언할 때 규칙이 있습니다.
// - 첫 글자는 대문자로
// - new 키워드로 인스턴스 생성
function Person(name, age) {
this.name = name;
this.age = age;
this.test = function() {
console.log(`${this.name} - ${this.age}`);
}
}
// OR
// 클래스 (es6 이후)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
test() {
console.log(`${this.name} - ${this.age}`);
}
}
// But. const Person = (name, age) => { ~~ } 는 작동하지 않는다.
// 생성자 함수와 this와 화살표 함수에 대한 자세한 내용은 추후에 다른 게시글로 작성하겠습니다.
function 키워드로 생성자 함수를 선언하거나 또는 class 키워드를 사용해서 클래스를 선언할 수 있습니다.
이렇게 만들어진 Person 클래스(또는 생성자 함수)의 [[Prototype]] 객체에 접근하는 방법을 알려드리겠습니다.
const a = new Person("양경민", 100);
console.log(a.name); // 양경민
console.log(a.age); // 100
console.log(a.__proto__); // {}
console.log(Object.getPrototypeOf(a)); // {}
console.log(a.__proto__ === Object.getPrototypeOf(a)); // true
Person.prototype.hobby = "자전거 타기";
console.log(a.__proto__); // { hobby: '자전거 타기' }
console.log(Object.getPrototypeOf(a)); // { hobby: '자전거 타기' }
console.log(a.__proto__ === Object.getPrototypeOf(a)); // true
Person 클래스의 prototype 속성으로 A 자신의 [[Prototype]]에 접근할 수 있으며 속성 또는 메서드를 추가할 수 있습니다.
그렇게 만들어진 속성과 메서드에 Person의 인스턴스가 접근할 수 있습니다.
또 a 인스턴스의 __proto__ 속성 또는 Object의 getPrototypeOf 메서드를 사용해 자신을 생성한 클래스(Person)의 [[Prototype]]에 접근할 수 있습니다.
왜 이렇게 작성하는지 내부적인 동작과정을 설명해드리겠습니다.

Person 클래스 선언시 해당 클래스만 만들어지는 것이 아니라 Person의 [[Prototype]] 객체도 같이 만들어집니다.
그렇게 만들어진 Person 클래스의 prototype 속성은 Person 클래스의 [[Prototype]]을 가리키게 되고,
Person 클래스의 [[Prototype]]에 있는 constructor는 Person 클래스를 가리키게 되어 서로 상호 의존적인 관계가 만들어집니다.
Person 클래스의 [[Prototype]] 또한 객체이기에 자신의 부모 클래스의 [[Prototype]]를 가리키는 속성을 가지고 있습니다.
이렇게 쭈욱 체인이 이어지다 __proto__가 null인 값을 가지는 객체(Object 클래스)가 최상위 객체인 것입니다.
+ 개인적으로 헷갈려서 추가적으로 알아본 내용을 공유해드립니다.
JavaScript의 특성상 Person 클래스 또한 내부적으로 생성자 함수로 만들어진 객체이기 때문에 이 객체가 가리키는 __proto__ 속성이 궁금했습니다.
이를 알아본 결과 다음과 같은 결과를 알아낼 수 있었습니다.
console.log(Person.__proto__); // ƒ () { [native code] }
console.log(Function.prototype); // ƒ () { [native code] }
console.log(Person.__proto__ === Function.prototype); // true
말씀드린바와 같이 객체의 __proto__속성은 부모 클래스의 [[Prototype]]을 가르킨다고 말씀드렸습니다.
Person객체는 Function 객체(생성자 함수)로 만들어지기 때문에 Function의 [[Prototype]]을 가리키는 결과를 알아낼 수 있었습니다.
3. 상속하는 법
class에서 상속받는 방법을 간단합니다. extends를 사용하면 되는데요!
하지만 생성자 함수에서 상속받는 방법은 살짝 다릅니다.
아래에서 Person 생성자 함수를 상속받은 Developer 생성자 함수를 만들어보겠습니다.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.introduce = () => {
console.log(`${this.name} - this.age`);
}
function Developer(name, age, laptop) {
Person.call(this, name, age);
this.laptop = laptop;
}
const yang = new Developer("양갱", 100, "moktop");
console.log(yang); // { name: '양갱', age: 100, laptop: 'moktop' }
이렇게 Object에서 상속받은 call 메서드로 Person의 속성들을 받아올 수 있었습니다.
하지만 한 가지 문제가 있습니다. Person의 프로토타입을 상속받은 것은 아니기에 introduce 메서드는 존재하지 않습니다.
Object의 getOwnPropertyNames 로 프로토타입을 확인해볼까요?
...
console.log(Object.getOwnPropertyNames(Person.prototype));
// => [ 'constructor', 'introduce' ]
console.log(Developer.prototype.__proto__);
// 아래 첨부된 사진을 확인해주세요.

보시는 바와 같이 Developer 생성자 함수의 프로토타입이 상속받는 프로토타입은 Person이 아닌, Object인 것을 확인할 수 있습니다.
그러므로 Developer에서는 introduce 메서드를 사용할 수 없죠.
이를 해결하기 위해서는 Person의 [[Prototype]] 또한 상속받아야 합니다.
이는 Object.create 메서드로 해결할 수 있습니다.
...
Developer.prototype = Object.create(Person.prototype);
Developer.prototype.constructor = Developer;
console.log(Developer.prototype.__proto__);
// => { introduce: [Function (anonymous)] }
4. 프로토타입 체인
Developer 생성자 함수는 Person 생성자 함수를 상속받기 때문에 Developer 생성자 함수로 만들어진 인스턴스에서는 introduce 메서드를 사용할 수 있습니다.
하지만 Developer의 메서드도, Developer의 [[Prototype]]메서드에도 존재하지 않는데 이를 어떻게 사용할 수 있을까요?
이는 프로토타입 체인 덕분입니다.
...
const yang = new Developer("양갱", 100, "moktop");
console.log(Object.getOwnPropertyNames(yang));
// => 먼저 yang의 메서드 중 introduce는 존재하지 않음.
// => 이후 Developer의 [[Prototype]]을 확인.
console.log(Object.getOwnPropertyNames(Developer.prototype));
// => Developer의 [[Prototype]]에도 introduce는 존재하지 않음.
// => 이후 [[Prototype]]의 __proto__인 Person의 [[Prototype]]을 확인.
console.log(Object.getOwnPropertyNames(Developer.prototype.__proto__));
// => introduce 메서드 존재하므로 실행.
이와 같이 현재 인스턴스에 존재하지 않으면 프로토타입을 확인하고, 그 프로토타입의 상위 프로토타입을 확인하는 식으로 쭉 이어갑니다.
Object의 valueOf, call 등의 메서드를 사용할 수 있는 이유도 이와 마찬가지입니다.
5. 마치며
오늘은 JavaScript의 prototype에 대해서 알아보았습니다.
실제 개발할 때에는 직접적으로 많이 사용하지 않는 기능이지만 JavaScript prototype의 내부적인 동작을 안다면 상속과 class를 사용할 때 더욱 효율적으로 사용할 수 있게 될 것입니다.
잘못된 내용이 있다면 댓글로 남겨주세요!
출처
Object prototypes - Web 개발 학습하기 | MDN
Javascript에서는 객체를 상속하기 위하여 프로토타입이라는 방식을 사용합니다. 본 문서에서는 프로토타입 체인이 동작하는 방식을 설명하고 이미 존재하는 생성자에 메소드를 추가하기 위해
developer.mozilla.org
prototype vs __proto__ - JavaScript 객체 지향 프로그래밍
수업소개 자바스크립트의 시크릿을 여는 열쇠 prototype과 __proto__에 대해서 알아봅시다. 강의
opentutorials.org
'JavaScript' 카테고리의 다른 글
| Javascript - Iteration Protocol (0) | 2022.05.29 |
|---|---|
| JavaScript Execution Context - 1 (2) | 2022.04.26 |