JavaScript는 흔히 프로토타입 기반 언어(prototype-based language)라 불립니다. — 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체(prototype object)를 가진다는 의미입니다. 프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고 그 상위 프로토타입 객체도 마찬가지입니다. 이를 프로토타입 체인(prototype chain)이라 부르며 한 객체에 정의된 메소드와 속성을 다른 객체에서 사용할 수 있도록합니다.
프로토타입 체인이란?
상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의 prototype이라는 속성에 정의되어 있습니다.
JavaScript에서는 객체 인스턴스와 프로토타입 간에 연결(많은 브라우저들이 생성자의 prototype 속성에서 파생된 __proto__ 속성으로 객체 인스턴스에 구현하고 있습니다.)이 구성되며 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메소드를 탐색합니다.
구조도
Object는 모든 클래스의 조상이기 때문에 __proto__에 null값이 할당되어있다.
부모 프로토타입을 자식이 사용 가능한 이유
자식에게서 부모의 프로토타입 메소드를 호출하면 브라우저는 우선 자식 객체가 해당 메소드를 가지고 있는지 체크하고 없으면 .__proto__링크를 타고 올라가서 부모의 프로토타입 객체를 체크한다. 거기에도 없으면 그 위에 부모의 프로토타입을 확인해서 거기에 있으면 호출하면서 끝이나고 없으면 더 이상 부모가 없는 객체까지 올라가는 식으로 프로토타입 체인을 계속 타고 올라가며 체크합니다.
예시
object
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(this.name + " said hello");
}
}
sleep() {
console.log(`${this.name}은 잠에 들었습니다`);
}
}
let steve = new Human('steve', 30);
위 코드에서 Object에 정의되어 있는 메소드를 호출하면 어떻게 될까요?
steve.valueOf()
브라우저는 우선 steve 객체가 valueOf() 메소드를 가지고 있는지 체크합니다. 없으므로 steve의 프로토타입 객체(Human() 생성자의 프로토타입)에 valueOf() 메소드가 있는지 체크합니다. 여전히 없으므로 Human() 생성자의 프로토타입 객체의 프로토타입 객체(Object() 생성자의 프로토타입)가 valueOf() 메소드를 가지고 있는지 체크합니다. 여기에 있으니 호출하며 끝납니다.
DOM과 프로토타입
브라우저에서 DOM을 이용하면, document.createElement('div')로 새로운 div 엘리먼트를 만들 수 있습니다. 이렇게 생성된 div 엘리먼트는, HTMLDivElement라는 클래스의 인스턴스입니다.
DOM 엘리먼트는 예를 들어 innerHTML과 같은 속성, 또는 append()와 같은 메서드가 있습니다. 각각의 엘리먼트가 해당 메서드나 속성이 있다는 것을 통해, Element라는 공통의 부모가 있음을 알 수 있습니다.
화살표 방향은 부모를 가리킵니다. EventTarget의 부모로는, 모든 클래스의 조상인 Object가 존재합니다.
인스턴스의 __proto__를 이용하면 이를 더 확실하게 확인할 수 있습니다. __proto__를 이용하면 부모 클래스의 프로토타입, 혹은 '부모의 부모 클래스'의 프로토타입을 탐색할 수 있습니다.
let div = document.createElement('div');
div.__proto__
div.__proto__.__proto__
div.__proto__.__proto__.__proto__
div.__proto__.__proto__.__proto__.__proto__
div.__proto__.__proto__.__proto__.__proto__.__proto__
div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__
div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__
프로토타입 상속
상위 클래스를 하위 클래스로 상속 받아오기 위해 ES6부터는 extends키워드와 super()연산자를 사용합니다.
- extends : 상속 받을 클래스를 명시
- super : 상위 클래스의 생성자를 호출, 매개변수를 통해 상위 클래스의 멤버(속성, 메서드)를 상속받음
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(this.name + " said hello");
}
sleep() {
console.log(`${this.name}은 잠에 들었습니다`);
}
}
class Teacher extends Human {
constructor(name, age, subject, grade) {
super(name, age);
// subject와 grade는 Teacher만의 속성
this.subject = subject;
this.grade = grade;
}
speak(){
return `${this.name}이(가) 수업을 진행합니다.`
}
}
Teacher의 인스턴스를 생성하면 이제 Teacher와 Human 양 쪽의 메소드와 속성을 사용할 수 있습니다.
let snape = new Teacher('Severus', 58, 'Dark arts', 5);
snape.sayHello(); // Severus said hello.
snape.speek(); // Severus이(가) 수업을 진행합니다.
snape.age // 58
snape.subject; // Dark arts
요약
- JavaScript는 프로토타입 기반 언어이며 OOP 상속을 구현할 때 프로토타입 체인을 사용한다.
- 프로토타입은 오프젝트 자료이면서 유전자다
- 부모클래스에서 상속된 자식 인스턴스들은 부모의 프로토타입을 사용 가능하다
- 자식들은 직접 프로토타입을 가지고 있지 않고 부모만 가지고 있지만 연결이 되어 있기 때문에 끌어다 사용이 가능하다.
- __proto__ : 객체와 객체를 연결하는 링크
- constructor는 생성자 함수 그 자체를 가리킴
- prototype은 생성자 함수에 정의한 모든 객체가 공유할 원형
- __proto__는 생성자 함수를 new로 호출할 때, 정의해두었던 prototype을 참조한 객체
- prototype은 생성자 함수에 사용자가 직접 넣는 것, __proto__는 new를 호출할 때 prototype을 참조하여 자동으로 만들어짐
- 생성자에는 prototype, 생성자로부터 만들어진 객체에는 __proto__
- 따라서 사용자는 prototype만 신경쓰면 된다. __proto__는 prototype이 제대로 구현되었는지 확인용으로 사용한다.
레퍼런스