19장 프로토타입
19.1 객체지향 프로그래밍
객체란?
속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조
19.2 상속과 프로토타입
- 상속 : 객체지향 프로그래밍의 핵심 개념
- 자바스크립트는 프로토타입을 기반으로 상속을 구현함
- 생성자 함수로부터 생성된 인스턴스의 메서드 중복 생성 방지에 유용함
// 생성자 함수
function Circle(radius) {
this.radius = radius;
}
// Circlr생성자 함수의 prototype프로퍼티에 바인딩하여
// 모든 인스턴스가 getArea메서드를 공유할 수 있도록 함
Circle.prototype.getArea = function () {
return Math.PI * this.radius ** 2;
};
const circle1 = new Circle(1);
const circle2 = new Circle(2);
19.3 프로토타입 객체
- 객체 간 상속을 구현하기 위해 사용됨
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지고, [[Prototype]]에 저장되는 프로토타입은 객체 생성방식에 의해 결정됨
- 모든 객체는 하나의 프로토타입을 갖고, 생성자 함수와 연결되어 있음
19.3.1 proto 접근자 프로퍼티
- 모든 객체는 proto접근자 프로퍼티를 통해 [[Prototype]]내부 슬롯에 간접적으로 접근할 수 있다.
- proto접근자 프로퍼티는 상속을 통해 사용됨 (Object.prototype의 프로퍼티임)
- 사용하는 이유 : 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함
- proto접근자 프로퍼티를 코드 내에 직접 사용하는 건 권장하지 않음 (모든 객체가 사용할 수 있는 게 아니기 때문)
- 프로토타입 참조를 취득하고 싶은 경우 : Object.getPrototypeOf메서드 사용 권장
- 프로토타입을 교체하고 싶은 경우 : Object.setPrototypeOf메서드 사용 권장
19.3.2 함수 객체의 prototype프로퍼티
- 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴
- proto접근자 프로퍼티와 prototype프로퍼티는 동일한 프로토타입을 가리키나 사용하는 주체가 다르다.
19.3.3 프로토타입의 constructor 프로퍼티와 생성자 함수
- 모든 프로토타입은 constructor프로퍼티를 가지고, 이는 prototype프로퍼티이며 자신을 참조하고 있는 생성자 함수를 가리킴
19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 리터럴 표기법에 의해 생성된 객체의 경우 가상적인 생성자 함수를 갖게 됨
19.5 프로토타입의 생성 시점
- 프로토타입은 생성자 함수가 생성되는 시점에 생성됨
19.5.1 사용자 정의 생성자 함수와 프로토타입 생성 시점
- construtor는 함수 객체를 생성하는 시점에 프로토타입시 생성됨
- 생성된 프로토타입의 프로토타입은 언제나 Object.prototype임
19.5.2 빌트인 생성자 함수와 프로토타입 생성 시점
- 빌트인 생성자 함수 : Object, String, Numer, Function, Array, RegExp, Date, Promise
- 전역 객체가 생성되는 시점에 생성됨
- 생성된 프로토타입은 빌트인 생성자 함수의 prototype프로퍼티에 바인딩 됨
19.6 객체 생성 방식과 프로토타입의 결정
- 객체생성방법 : 객체 리터럴, Object생성자 함수, 생성자 함수, Object.create메서드, 클래스(ES6)
- 자바스크립트 엔진은 객체를 생성할 때 추상 연산 OrdinaryObjectCreate를 호출함
19.6.1 객체 리터럴에 의해 생성된 객체의 프로토타입
- 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype임
19.6.2 Object생성자 함수에 의해 생성된 객체의 프로토타입
- Object생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype임
19.6.3 생성자 함수에 의해 생성된 객체의 프로토타입
- 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype프로퍼티에 바인딩 되어 있는 객체임
19.7 프로토타입 체인
- 정의: 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]]내부 슬롯의 참조를 따라 부모역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것
- 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘
- Object.prototype : 프로토타입 체인의 종점
- 스코프체인과 프로토타입 체인은 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용됨
19.8 오버라이딩과 프로퍼티 섀도잉
- 오버라이딩: 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
- 오버로딩: 함수의 이름은 동일하지만 매개변수에 의해 메서드를 구별하여 호출하는 방식 (자바스크립트는 지원하지 않음)
- 프로퍼티 섀도잉 : 상속관계에 의해 프로퍼티가 가려지는 현상
- 하위객체를 통해 프로토타입의 프로퍼티 변경 / 삭제는 불가능함
19.9 프로토타입의 교체
19.9.1 생성자 함수에 의한 프로토타입의 교체
- 생성자 함수의 prototype프로퍼티를 통해 프로토타입을 교체할 수 있는데, 이 때 constructor프로퍼티와 생성자 함수 간의 연결이 파괴되므로 constructor 프로퍼티를 추가하여 연결을 되살릴 수 있음
const Person = (function () {
function Person(name) {
this.name = name;
}
// 생성자 함수의 prototype프로퍼티를 통해 프로토타입 교체
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
}());
19.9.2 인스턴스에 의한 프로토타입의 교체
- 인스턴스의 proto접근자 프로퍼티(또는 Object.getPrototypeOf메서드)를 통해 프로토타입을 교체할 수 있는데, 이 때 constructor프로퍼티와 생성자 함수 간의 연결이 파괴되므로 constructor 프로퍼티를 추가하여 연결을 되살릴 수 있음
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// 프로토타입으로 교체할 객체
const parent = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
// me객체의 프로토타입을 parent객체로 교체한다.
// 아래 코드는 me.__proto__ = parent; 와도 같다.
Object.setPrototypeOf(me, parent);
19.10 instanceof 연산자
객체 instanceof 생성자 함수
- 우변에 생성자 함수의 prototype에 바인딩된 객체가 좌변의 프로토타입 체인상 존재여부에 따라 bool값으로 반환
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가됨
console.log(me instanceof Person); // true
19.11 직접 상속
19.11.1 Object.create에 의한 직접 상속
- 명시적으로 프로토타입을 지정하여 새로운 객체를 생성하는 메서드
- 첫번째 매개변수: 프로토타입으로 지정할 객체
- 두번째 매개변수: 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체 (옵션, 생략가능)
const obj = Object.create(Object.prototype, {
x: { value: 1, writable: true, enumerable: true, configurable: true }
});
19.11.2 객체 리터럴 내부에서 proto에 의한 직접 상속 (ES6)
const myProto = { x: 10 };
const obj = {
y: 20,
__proto__: myProto
};
19.12 정적 프로퍼티/메서드
- 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드 (프로토타입 체인상의 프로퍼티/메서드가 아닌 생성자 함수 자체가 가지고 있는 프로퍼티/메서드임)
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function() {
console.log(`Hi! My name is ${this.name}`);
}
// 정적 프로퍼티
Person.staticProp = 'static prop';
// 정적 메서드
Person.staticMethod = function () {
console.log('staticMethod');
}
19.13 프로퍼티 존재 확인
19.13.1 in 연산자
- 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인
key in object
const person = {
name: 'Lee',
address: 'Seoul'
};
// person객체에 name프로퍼티가 존재한다
console.log('name' in person); // true
- ES6에서 도입된 Reflect.has메서드를 사용해도 동일하게 작동함
console.log(Reflect.has(person, 'name')); // true
19.13.2 Object.prototype.hasOwnProperty메서드
- 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고, 상속받은 키인 경우엔 false를 반환함
console.log(person.hasOwnProperty('name'));
19.14 프로퍼티 열거
19.14.1 for...in문
- 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]값이 true인 프로퍼티를 순회하며 열거함 (순서를 보장하지 않음)
- 순회 대상 객체의 프로퍼티 뿐만 아니라 상속받은 프로퍼티까지 열거됨
for (변수선언문 in 객체) { ... }
const person = {
name: 'Lee',
address: 'Seoul'
};
// for...in문의 변수 prop에 perso객체의 프로퍼티 키가 할당됨
for (const key in person) {
console.log(key + ': ' + person[key]);
}
// name: Lee
// address: Seoul
19.14.2 Object.keys/values/entries메서드
1. Object.keys
- 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
2. Object.values (ES8)
- 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환
3. Object.entries (ES8)
- 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
const person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 }
};
console.log(Object.keys(person)); // ['name', 'address']
console.log(Object.values(person)); // ['Lee', 'Seoul']
console.log(Object.entries(person)); // [['name', 'lee'], ['address', 'Seoul']]