1. 프로토타입(prototype)
- 자바스크립트의 모든 객체는 자신의 부모 역할을 하는
프로토타입(prototype)
이라는 객체를 가지고 있으며, 객체 지향의 상속과 같이 모든 객체는 그 객체의 부모 객체인 프로토타입으로부터 프로퍼티와 메서드를 상속받는다.
- 자바스크립트의 모든 객체는 최소한 하나 이상의 다른 객체로부터 상속을 받으며, 이 때 상속되는 정보를 제공하는 객체를
프로토타입(prototype)
이라고 한다.
- 아래 예제에서 kim과 park은 eyes와 nose를 공통적으로 가지고 있는데, 메모리에는 eyes와 nose가 두 개씩 총 4개 할당된다. 객체를100개 만들면 200개의 변수가 메모리에 할당되는 문제가 발생한다.
function Person() {
this.eyes = 2;
this.nose = 1;
}
var kim = new Person();
var park = new Person();
console.log(kim.eyes);
console.log(kim.nose);
console.log(park.eyes);
console.log(park.nose);
- Person.prototype이라는 빈 Object가 어딘가에 존재하고, Person 함수로부터 생성된 객체(kim, park)들은 어딘가에 존재하는 Object에 들어있는 값을 모두 가져다 쓸 수 있다. 즉, eyes와 nose를 어딘가에 있는 빈 공간에 넣어놓고 kim과 park이 공유해서 사용하는 것이다.
function Person() {}
Person.prototype.eyes = 2;
Person.prototype.nose = 1;
var kim = new Person();
var park = new Person():
console.log(kim.eyes);
...
1) prototype 프로퍼티
prototype
프로퍼티는 함수 객체만 가지고 있는 프로퍼티(constructor
를 소유하는 프로퍼티)로, 생성자 함수가 생성하는 인스턴스의 프로토타입을 가리킨다.
function func() {}
func.hasOwnProperty('prototype')
const obj = {}
obj.hasOwnProperty('prototype')
prototype
프로퍼티를 사용하면 현재 존재하고 있는 프로토타입에 새로운 프로퍼티나 메서드를 추가할 수 있다.
function Dog(color, name, age) {
this.color = color;
this.name = name;
this.age = age;
}
Dog.prototype.family = "시베리안 허스키";
Dog.prototype.breed = function () {
return this.color + " " + this.family;
};
var myDog = new Dog("흰색", "마루", 1);
var hisDog = new Dog("갈색", "콩이", 3);
console.log("우리 집 강아지는 " + myDog.family + "이고, 친구네 집 강아지도 " + hisDog.family + "입니다.");
console.log("우리 집 강아지의 품종은 " + myDog.breed() + "입니다.");
console.log("친구네 집 강아지의 품종은 " + hisDog.breed() + "입니다.");
2) constructor(생성자 함수)
constructor(생성자)
는 class
로 생성된 인스턴스 객체를 생성하고 초기화하기 위한 특수한 메서드이다.
- 클래스 내에서 생성자 함수는 하나만 존재할 수 있다.
- 디폴트 프로퍼티
prototype
은 constructor
프로퍼티 하나만 있는 객체를 가리키는데, 여기서 constructor
프로퍼티는 함수 자신을 가리킨다.
- 생략이 가능하여 생략시 빈 constructor, 즉 빈 객체가 생성된다.
- 생성자 함수를 작성하지 않으면, 기본 생성자(default constructor)가 제공되며, 기본(base) 클래스일 경우는 기본 생성자는 비어있으며, 파생(derived) 클래스일 경우 기본 생성자는 부모 생성자를 부른다.
class Computer {
}
let cpu = new Computer()
console.log(cpu)
3) __proto__
/ [[Prototype]] 프로퍼티
- 모든 자바스크립트 객체는 자신의 프로토타입을 가리키는
[[Prototype]]
을 가지고 있으며, __proto__
접근자 프로퍼티를 통해 자신의 프로토타입인 [[Prototype]]
내부 슬롯에 접근하여 상위 객체 역할을 하는 생성자 함수의 prototype
객체의 내부에 정의된 모든 프로퍼티와 메서드를 사용할 수 있다.
[[Prototype]] : 내부 슬롯(internal slot)
- 자바스크립트의 객체는 명세서에서 명명한
[[Prototype]]
이라는 숨김 프로퍼티를 갖는다. 이 숨김 프로퍼티 값은 null
이거나 다른 객체에 대한 참조가 되는데, 다른 객체를 참조하는 경우 참조 대상을 '프로토타입(prototype)'이라 부른다.
2-1. __proto__
는 접근자 프로퍼티이다.
- 원칙적으로
[[prototype]]
는 내부 슬롯이므로 프로퍼티가 아니고, 내부 슬롯이나 내부 메서드에 직접 접근하거나 호출할 수는 없다. 따라서 __proto__
접근자 프로퍼티를 통해 간접적으로 [[prototype]]
의 값, 즉 상위 객체의 프로토타입에 접근할 수 있다.
__proto__
는 [[Prototype]]
에 접근하기 위한 수단이지 [[Prototype]]
그 자체가 아니다.
__proto__
는 [[Prototype]]
의 getter
(획득자)이자 setter
(설정자) 이다.
const obj = {};
const parent = {
x: 1
};
console.log(obj.__proto__);
obj.__proto__ = parent;
console.log(obj.__proto__);
console.log(obj.x);
- 인스턴스의
__proto__
접근자 프로퍼티가 가리키는 객체와 해당 인스턴스를 생성한 생성자 함수의 prototype
프로퍼티가 가리키는 객체는 같다.
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sleep() {
console.log(`${this.name}은 잠에 들었습니다`);
}
}
let kimcoding = new Human('김코딩', 30);
Human.prototype.constructor === Human;
Human.prototype === kimcoding.__proto__;
Human.prototype.sleep === kimcoding.sleep;
2-2. __proto__
는 상속을 통해 사용된다.
__proto__
접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype
의 프로퍼티다. 모든 객체는 상속을 통해 Object.prototype.__proto__
접근자 프로퍼티를 사용할 수 있다.
const person = { name: 'Lee' };
console.log(person.hasOwnProperty('__proto__'));
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
console.log({}.__proto__ === Object.prototype);
2-3. __proto__
를 통해 프로토타입에 접근하는 이유
__proto__
를 굳이 사용하여 [[prototype]]
의 값에 접근하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서다.
- 아래 예제 처럼 두 객체가 서로의 프로토타입으로 설정하는 것이 에러 없이 정상적으로 작동한다면 비정상적인 프로토타입 체인이 만들어지기 때문에,
__proto__
는 에러를 발생시킨다.
const parent = {};
const child = {};
child.__proto__ = parent;
parent.__proto__ = child;
- 프로토타입 체인은 무조건 단방향 리스트로 구현되야 한다. 즉, 프로퍼티 검색 방향이 한쪽 방향으로만 흘러가야 한다. 만약 서로 참조하는 순환형 프로토타입 체인이 만들어지게 된다면 프로토타입 체인의 종점이 존재하지 않기 때문에 프로퍼티를 검색할 때 무한루프에 빠지게 된다. 따라서 아무런 체크 없이 무조건적으로 프로토타입을 교체할 수 없도록
__proto__
를 이용해 프로토타입을 교체하도록 구현했다.
2-4. __proto__
사용은 권장하지 않는다.
__proto__
는 ES5까지는 ECMAScript 사양에 포함되지 않은 비표준이었다. 하지만 일부 브라우저에서 지원하기 때문에 호환성을 고려하여 ES6에서 __proto__
를 표준으로 채택했다. 다만, 아래 예제처럼 모든 객체가 __proto__
를 사용할 수 있는 것은 아니기 때문에 실제 코드 내에서 __proto__
를 직접 사용하는 것은 권장하지 않는다.
const obj = Object.create(null);
console.log(obj.__proto__);
- 따라서
__proto__
대신하여 아래의 메서드를 사용한다.
Object.getPrototypeOf(obj)
: obj의 [[prototype]]
의 값을 참조한다.
Object.setPrototypeOf(obj)
: obj의 [[Prototype]]
이 proto가 되도록 교체한다.
2. 프로토타입 체인(prototype chain)
- 프로토타입 체인은
__proto__
의 특징을 이용하여, 부모 객체의 프로퍼티나 메서드를 차례로 검색하는 것을 의미한다. 즉, 특정 객체의 프로퍼티나 메서드 접근 시 자신의 것 뿐만 아니라 부모 객체의 것에도 접근하여 사용가능하다는 것을 말한다.
- 현재 객체의
__proto__
프로퍼티를 참조해서 해당 프로퍼티가 있는지 체크하고, 그래도 없으면 부모의 __proto__
프로퍼티를 참조해서 해당 프로퍼티를 체크하는 것을 말한다.
- 모든 프로토타입 체인의 종점은
Object.prototype
이고, 최종 Object.prototype
객체까지 해당하는 프로퍼티가 존재하지 않는다면 undefined
를 반환한다.
function Ultra(){}
Ultra.prototype.ultraProp = true;
function Super(){}
Super.prototype = new Ultra();
function Sub(){}
Sub.prototype = new Super();
var hero = new Sub();
console.log(hero.ultraProp);
1) 메서드 오버라이딩(method overridng)
- 오버라이딩이란 자바스크립트 객체의 상속받은 부모의 메서드를 자식 클래스에서 재정의(덮어씌우기)하는 것을 의미한다.(오버라이딩이라는 개념은 존재하지만 자바같은 언어와는 다르다)
- 원본을 제거하고 다른 대상으로 교체하는 것이 아닌, 원본이 그대로 있는 상태에서 다른 대상을 그 위에 덮어씌운 상황을 말한다.
class Parent {
constructor (name) {
this.name = name;
}
who () {
return this.name + ' is father';
}
}
const parent = new Parent('Tom');
console.log(parent.who);
class Child extends Parent {
constructor(name) {
super(name);
}
who () {
return this.name + ' is child';
}
}
const child = new Child('Max');
console.log(child.who);