들어가며
- 이 내용은 코어 자바스크립트의 강의 중 prototype과 class를 정리한 내용 입니다.
- 강의링크
프로토타입
- 상속되는 속성과 메소드들이 정의되어 있는 곳의 속성을 의미1
- 생성자 함수와 new2 키워드를 이용해서 instance를 생성하면 Constructor3의
prototype
이라고 하는 프로퍼티의 내용이 instance의[[Prototype]]
라고 하는 프로퍼티로 참조를 전달하게 된다.
리터럴의 경우
- 리터럴의 경우에는 instance가 아니므로
[[Prototype]]
이 존재하지 않는다. - 하지만 메서드 처럼 접근이 가능한데, 가능한 이유는 javascript에서 타입에 해당되는 객체의 인스턴트를 임시로 만들어서 메서드를 호출하기 때문에 가능하다.
- 메서드 호출이 끝나고 임시로 만든 instance는 삭제 된다.
프로토타입 체이닝
- Constructor의
prototype
은 Object 생성자 함수의 new 연산으로 생성된 instance이다. - instance는 모두
[[Prototype]]
을 가지고 있으므로 Constructor의prototype
의[[Prototype]]
은 Object의prototype
이 된다. - 따라서 Object의
prototype
도 접근할 수 있게 된다. - 그러므로 Object 전용 메서드를 Object의
prototype
에 정의하면 안된다. 왜냐면 다른 객체에서 호출 할 수 있기 때문이다. - 따라서 유독
Object.xxxx(obj);
형태의 메서드가 많은 이유가 프로토타입 체이닝 특성 때문이다.
예시
Ararry에는 toString이라는 메서드가 있다. 그래서 배열의 toString을 호출하면 아래와 같이 나오는데, 이는 Array 객체의
prototype
에 존재하는 toString 메서드를 호출 하기 때문이다.1 2
const arr = [1,2,3]; arr.toString(); // "1,2,3"
만약 Array 객체의
prototype
에 존재하는 toString를 지우면 다음과 같이 나올 것이다.1 2 3
const arr = [1,2,3]; delete Array.prototype.toString; arr.toString(); // "[object Array]"
이유는 이제 Array 객체의
prototype
에는 toString 메서드가 존재 하지 않기 때문에, 프로토타입 체이닝을 통해서Object의 prototype
에 존재하는 toString 메서드를 호출 하기 때문이다. 이것도스코프 체인
과 비슷하게 근접한 곳에서 부터 탐색한다.
프로토타입을 응용한 class 구현
ES6 전에는 javascript에 class라는 키워드가 존재하지 않았다. 따라서 아래와 같이 구현한다.
기본적인 구현 방법
- 부모, 자식 객체의 생성자 함수를 만든다. (Food, Apple)
- 부모 객체에서 자식 객체에게 상속시킬 메서드를 prototype에 정의 한다.
- 자식의
prototype
을 부모의 새로운 instance로 대체한다. - 자식의
prototype
의constructor
는 부모의 객체로 대체 되었기 때문에 다시 자식의 객체로 변경한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Food(color){
this.color = color;
}
function Apple(color){
this.color = color;
}
// 부모 객체에서 자식 객체에게 상속시킬 메서드를 prototype에 정의 한다.
Food.prototype.printFoodColor = function() {
console.log(`Color:`, this.color);
}
// 자식의 prototype을 부모의 새로운 instance로 대체한다.
Apple.prototype = new Food();
// 자식의 prototype의 constructor는 부모의 객체로 대체 되었기 때문에 다시 자식의 객체로 변경한다.
Apple.prototype.constructor = Apple;
const apple = new Apple('green');
apple.printFoodColor(); // Color: green
기본적인 개념은 다음과 같은데, 여기서 보완해야될 부분이 있다.
- Food의 color가 Apple의 prototype에 남게 된다.
- Apple에도 color를 지정하고 Food에서도 color를 지정하는 코드 중복이 일어난다.
보완한 구현 방법 1 - 중복 프로퍼티 제거
- Bridge라는 빈 객체를 만든다.
- Bridge의 prototype에 부모의 prototype을 대입한다.
- Bridge의 새로운 인스턴스를 자식의 prototype에 대입한다.
1
2
3
4
5
6
7
8
9
10
11
12
// Bridge라는 빈 객체를 만든다.
function Bridge(){};
// Bridge의 prototype에 부모의 prototype을 대입한다.
Bridge.prototype = Food.prototype;
// Bridge의 새로운 인스턴스를 자식의 prototype에 대입한다.
Apple.prototype = new Bridge();
Apple.prototype.constructor = Apple;
const apple = new Apple('green');
apple.printFoodColor();
이렇게 되면 기존에 Food에서는 생성자를 통해서 color이라는 속성이 생겼지만 Bridge 생성자에서는 아무것도 하지 않으므로 color라는 속성이 생기지 않게되므로, Food의 prototype만을 Apple에게 상속시킬 수 있게 된다.
보완한 구현 방법 2 - 생성자 함수 중복코드 제거
- Bridge라는 빈 객체를 만든다.
- Bridge의 prototype에 부모의 prototype을 대입한다.
- Bridge의 새로운 인스턴스를 자식의 prototype에 대입한다. (여기까지 동일)
- 자식의 prototype의 superClass에 부모를 지정한다. (이름은 상관없음)
- 자식 생성자 함수에서 superClass를 호출한다. 호출된 superClass는 메서드로써 호출되었기 때문에
thisBind
에 의해서 this가 자식의 instance가 되므로 color가 자식의 instance의 프로퍼티가 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Food(color){
this.color = color;
}
function Apple(color){
// 자식 생성자 함수에서 superClass를 호출한다.
this.superClass(color);
}
// Bridge라는 빈 객체를 만든다.
function Bridge(){};
// Bridge의 prototype에 부모의 prototype을 대입한다.
Bridge.prototype = Food.prototype;
// Bridge의 새로운 인스턴스를 자식의 prototype에 대입한다.
Apple.prototype = new Bridge();
Apple.prototype.constructor = Apple;
// 자식의 prototype의 superClass에 부모를 지정한다.
Apple.prototype.superClass = Food;
const apple = new Apple('green');
apple.printFoodColor();
참고 - 상속관계를 함수화
위와 같은 코드의 구성은 부모 자식 관계를 맺을 때마다 작성하는 귀찮다. 따라서 아래와 같은 함수를 만들어서 쉽게 상속관계를 맺을 수 있도록 하는것을 추천하고 있다고 한다.
1
2
3
4
5
6
7
8
9
10
11
var extendClass = (function() {
function Bridge(){}
return function(Parent, Child) {
Bridge.prototype = Parent.prototype;
Child.prototype = new Bridge();
Child.prototype.constructor = Child;
Child.prototype.superClass = Parent;
}
})();
extendClass(Food, Apple);