프로토타입
- 프로토타입은 객체가 가진 기본 속성과 메서드를 정의하는 객체로써 객체간의 상속을 구현하는데 사용된다.
- 모든 자바스크립트 객체는 다른 객체로부터 속성과 메서드를 상속받을 수 있는 숨겨진 링크(프로토타입 체인)를 가지고 있다.
프로토타입의 구조와 개념
어떤 생성자 함수(constructor)를 new 연산자와 함께 호출하면 constructor에서 정의된 내용을 바탕으로 새로운 instance가 생성된다.
이때 instance에는 __proto__라는 constructor의 prototype 프로퍼티를 참조하는 프로퍼티가 자동으로 부여된다.
// Constructor : 함수 객체이며, prototype 프로퍼티를 가짐
function Person(name) {
this.name = name;
}
// prototype에 메서드 추가
// prototype : 모든 인스턴스가 공유하는 메서드와 속성을 저장
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
// Person 생성자로 만들어진 인스턴스
// __proto__로 Person.prototype 참조 가능
const john = new Person('John');
1. Constructor
console.log(Person.prototype.constructor === Person); // true
console.log(john.constructor === Person); // true
- 객체를 생성하는 생성자 함수
- constructor.prototype 에는 constructor 라는 프로퍼티가 있는데, 이는 다시 생성자 함수 자신을 가리킨다.
- constructor 는 인스턴스가 자신의 생성자 함수가 무엇인지 식별할 수 있는 속성이다.
- prototype을 확인하기 위해 constructor를 확인해볼 수 있지만, 변경 가능하므로 항상 안전한 것은 아님
2. prototype
- 생성자 함수의 프로토타입 객체
- 모든 인스턴스가 공유하는 메서드와 속성을 저장
- 생성자 함수만 가진 속성
- 해당 함수로 생성될 객체들이 상속받을 프로토타입 객체를 가리킴
3. instance
- 생성자 함수(constructor)로 만들어진 객체
- __proto__ 로 생성자 함수의 prototype 을 참조할 수 있다.
console.log(john instanceof Person); // true
console.log(john instanceof Object); // true (모든 객체는 Object의 인스턴스)
- instanceof 연산자로 객체가 특정 생성자 함수의 프로토타입 체인에 속하는지 확인할 수 있다.
4. __proto__
// 프로토타입 접근 방법들
console.log(john.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(john) === Person.prototype); // true
// 권장되는 프로토타입 접근 방법
const proto = Object.getPrototypeOf(john); // 객체의 프로토타입 반환
const newObj = Object.create(Person.prototype); // 새로운 객체 생성, 특정 프로토타입으로 설정
- 모든 객체가 가진 속성
- 해당 객체의 부모 프로토타입 객체를 참조(객체의 프로토타입 링크)
- __proto__는 생략가능한 속성이라 인스턴스는 constructor.prototype의 메서드를 마치 자신의 메서드인 것처럼 호출할 수 있다.
- prototype 프로퍼티 내부의 메서드만 접근할 수 있으며 외부 메서드들은 생성자 함수에서 직접 접근해야한다.
- ES6부터 비표준 간주 및 브라우저 호환성과 성능을 위해 Object.getPrototypeOf() / Object.create() 사용 권장
■ prototype과 __proto__ 의 주요 차이점
- prototype은 생성자 함수에만 존재
- __proto__는 모든 객체에 존재
- 인스턴스는 __proto__를 통해 생성자의 prototype에 접근할 수 있다.
프로토타입 체인
- 객체들이 서로 연결된 구조
- 각 객체는 다른 객체를 가리키는 내부 링크(프로토타입)를 가지고 있으며, 이 링크들이 연결되어 체인을 형성한다.
프로토타입 체인의 작동 방식
- 객체에서 프로퍼티나 메서드를 찾을 때, JS 엔진은 먼저 해당 객체를 검색한다.
- 해당 객체에 찾는 프로퍼티나 메서드가 없으면, 객체의 프로토타입으로 이동해서 다시 검색한다.
- 이 과정을 프로토타입 체인의 끝(보통 Object.prototype)에 도달할 때까지 반복한다.
- 끝까지 찾지 못할경우 undefined를 반환한다.
→ 이런식으로 찾아가는 과정을 프로토타입 체이닝이라고 하며, 이 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있다.
메서드 오버라이드(Method Override)
- 하위 객체에서 상위 객체의 메서드를 동일한 이름으로 재정의하는 것
- 과도한 오버라이드는 코드 복잡성을 증가시키므로 성능과 가독성을 고려해야한다.
const parent = {
greet() {
console.log("안녕하세요!");
}
};
const child = Object.create(parent);
child.greet = function() {
console.log("안녕!");
};
child.greet(); // "안녕!"
객체 메서드의 예외사항
모든 생성자 함수의 prototype 은 반드시 객체이므로 Object.prototype 이 언제나 프로토타입 체인의 최상단에 존재한다.
그에따라 객체에서만 사용할 메서드는 다른 데이터 타입처럼 프로토타입 객체안에 정의 할 수가 없다.
→ 객체에서만 사용할 메서드를 Object.prototype 내부에 정의하면 다른 데이터 타입도 해당 메서드를 사용할 수 있기 때문!
- Object.prototype 의 메서드 - 모든 객체가 상속받아 사용 가능
Object.prototype.toString();
Object.prototype.hasOwnProperty();
Object.prototype.valueOf();
Object.prototype.isPrototypeOf();
⋮
- 객체 전용 메서드 - 다른 데이터 타입과 달리 Object 생성자 함수에 static 하게 담겨있다.
- 정적 메서드는 객체 인스턴스가 아닌 Object 생성자 함수를 통해 직접 호출해야 한다.
- 객체에서만 사용할 메서드는 Object.prototype이 아닌 정적 메서드로 정의해야 한다.
Object.keys();
Object.values();
Object.entries();
Object.assign();
Object.create();
⋮
다중 프로토타입 체인
- 계층적 상속 구조를 구현할 때 사용하고 여러 단계의 상속 메서드와 프로퍼티에 접근 가능하다.
- 과도하게 깊은 상속 구조는 성능 저하 가능성이 있으므로 신중하게 사용 or ES6의 class 문법을 사용하는 것을 권장한다.