📌 프로토타입 객체
프로토타입 객체는 객체 간 상속을 구현하기 위해 사용됩니다.
프로토타입은 어떤 객체의 상위 객체의 역할을 하며, 다른 객체에게 공유 프로퍼티(메서드도)를 제공할 수 있습니다.
모든 객체는 [[Prototype]]
이라는 내부 슬롯을 가집니다. 슬롯의 값은 프로토타입의 참조입니다.
객체가 생성될 때, 객체 생성 방식에 따라 프로토타입이 결정되어 [[Prototype]]
에 저장됩니다.
모든 객체는 하나의 프로토타입을 갖고, 모든 프로토타입은 생성자 함수와 연결되어 있습니다.
📌 __proto__
접근자 프로퍼티
모든 객체는 __proto__
접근자 프로퍼티로 [[Prototype]]
내부 슬롯에 간접적으로 접근할 수 있습니다.
Object.prototype
의 접근자 프로퍼티인 __proto__
는 getter/setter 함수라고 부르는 접근자 함수를 통해 [[Prototype]]
내부 슬롯의 값 → 프로토타입 을 얻거나 할당합니다.
const obj = {};
const parent = {x:1};
obj.__proto__; // get __proto__가 호출되어 obj 객체의 프로토타입을 얻음
console.log(obj.x); // undefined
obj.__proto__ = parent; // set __proto__가 호출되어 obj 객체의 프로토타입 교체
console.log(obj.x); // 1
__proto__
접근자 프로퍼티를 통해 프로토타입에 접근하는 이유?
const child = {};
const parent = {};
child.__proto__ = parent;
parent.__proto__ = child; // Error
이 코드를 정상적으로 실행시키면 서로가 자신의 프로토타입이 되어버리는 이상한 프로토타입 체인 → 순환 참조하는 프로토타입 체인이 만들어지기 때문에 __proto__
접근자 프로퍼티는 에러를 발생시킵니다.
따라서 확인 없이 프로토타입을 교체할 수 없도록 __proto__
접근자 프로퍼티를 사용해 프로토타입에 접근하고 교체하도록 구현해야 합니다.
하지만 __proto__
는 별로입니다...!
코드 내에서 __proto__
는 권장되지 않는다고 합니다. 모든 객체가 __proto__
를 사용할 수 있는 것은 아니기 때문입니다.
const obj = Object.create(null);
console.log(obj.__proto__); // undefined
console.log(Object.getPrototypeOf(obj)); // null
Object
는 프로토타입 체인의 종점이기 때문에, Object.__proto__
를 상속 받을 수 없습니다.
따라서 프로토타입의 참조를 얻고 싶다면 Object.getPrototypeOf
, 교체하고 싶다면 Object.setPrototypeOf
메서드를 사용할 것이 권장됩니다.
📌 함수 객체의 prototype 프로퍼티
함수 객체는 `prototype` 프로퍼티를 소유하지만 일반 객체는 소유하지 않습니다.
prototype
프로퍼티는 생성자 함수가 생성할 객체의 프로토타입을 가리킵니다.
따라서 생성자 함수로서 호출할 수 없는 함수인 화살표 함수와 축약 표현으로 정의한 메서드는 protoype
프로퍼티를 소유하지 않고 프로토타입도 생성하지 않습니다.
생성자 함수로 호출하기 위해 정의하지 않은 일반 함수도 prototype
프로퍼티를 소유하지만, 객체를 생성하지 않는 일반 함수의 prototype
프로퍼티는 의미가 없습니다.
결국 모든 객체가 가지고 있는 __proto__
접근자 프로퍼티와 함수 객체만 가지고 있는 prototype
프로퍼티는 동일한 프로토타입을 가리킵니다.
구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
__proto__ | 모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해서 사용 |
prototype 프로퍼티 | constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신이 생성할 객체의 프로토타입을 할당하기 위해 사용 |
📌 프로토타입의 생성 시점
프로토타입은 생성자 함수가 생성되는 시점에 같이 생성됩니다.
프로토타입과 생성자 함수는 언제나 쌍으로 존재하기 때문입니다.
사용자 정의 생성자 함수와 프로토타입 생성 시점
생성자 함수로서 호출할 수 있는 함수, 즉 constructor
는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됩니다.
// 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 같이 생성
console.log(Person.prototype); // {constructor: f}
// 생성자 함수
function Person(name) {
this.name = name;
}
const Person = name => {
this.name = name;
}
// non-constructor는 프로토타입이 생성되지 않습니다.
console.log(Person.prototype); // undefined
함수 선언문은 런타임 이전에 먼저 실행됩니다. 따라서 Person
생성자 함수는 먼저 평가되어 함수 객체가 됩니다. 이때 프로토타입도 같이 생성이 되는 것입니다.
생성된 프로토타입은 Person
생성자 함수의 prototype
프로퍼티에 바인딩됩니다.
📌 여러 가지 방식으로 생성된 객체의 프로토타입
객체 리터럴
const obj = { x: 1 };
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty('x')); // true
객체 리터럴에 의해 생성된 obj 객체는 Object.prototype
을 상속받습니다.
Object 생성자 함수
const obj = new Object();
obj.x = 1;
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty('x')); // true
Object 생성자 함수에 의해 생성된 obj 객체는 Object.prototype
을 상속받습니다.
생성자 함수
function Person() {
this.name = name;
}
const p = new Person('jack');
console.log(p.__proto__); // {constructor: f}
console.log(p.constructor); // f Person() {...}
console.log(p.hasOwnProperty('name')); // true
생성자 함수에 의해 생성된 객체의 프로토타입은 생성자 함수의 prototype
프로퍼티에 바인딩되어 있는 객체입니다.
사용자 정의 생성자 함수 Person
과 같이 생성된 Person.prototype
의 프로퍼티는 constructor
뿐입니다.
📌 프로토타입 체인
프로토타입 체인이란 자바스크립트에서 객체의 프로퍼티에 접근하려고 할 때, 프로퍼티가 없다면 [[Prototype]]
의 참조를 따라 자신의 상위 객체의 프로퍼티를 차례대로 검색하는 것을 말합니다.
자바스크립트가 객체 지향 프로그래밍에서 상속을 구현할 때 프로토타입 체인 개념을 사용합니다.
객체 리터럴 방식
const tom = {
name: 'Tom',
age: 21,
greeting() {
console.log(`Hi! My name is ${this.name}`);
}
}
console.log(tom.name); // Tom
tom.greeting(); // Hi! My name is Tom
console.log(tom.hasOwnProperty('name')); // true
tom.sayHi(); // TypeError: tom.sayHi is not a function
객체 tom
에서 name
속성과 greeting
메서드가 존재하므로 정상적으로 실행되고, sayHi
메서드는 존재하지 않으므로 에러를 발생시킵니다.
하지만 hasOwnProperty
메서드는 존재하지 않는데 정상적으로 실행되었습니다.
왜 그럴까요?
객체 tom
은 Object
리터럴로 생성되었으므로 Object()
라는 생성자 함수로 생성되었습니다.
Object()
는 함수이므로 prototype
프로퍼티가 존재하고, 생성자 객체는 Object.prototype
을 자신의 프로토타입 객체로 연결합니다.
Object.prototype
에는 hasOwnProperty
메서드가 존재하므로 정상적으로 실행된 것이었습니다.
생성자 함수 방식
function Person(name) {
this.name = name;
}
const tom = new Person('tom');
console.log(tom.hasOwnProperty('name')); // true
객체 tom
은 생성자 함수 Person
으로 생성했습니다. 따라서 tom
의 [[Prototype]]
은 Person
의 prototype
프로퍼티로 설정됩니다.
그런데 Person prototype 프로퍼티에는 hasOwnProperty
메서드가 없습니다.
그래서 에러가 발생되어야 할 것 같은데 잘 실행되어서 true
가 반환되었습니다.
프로토타입 체인이 발생한 것입니다.
과정
hasOwnProperty
를 호출한tom
객체에서hasOwnProperty
를 검색합니다.- 존재하지 않으므로 프로토타입 체인을 따라서
[[Prototype]]
에 바인딩 되어 있는 프로토타입인Person.prototype
으로 이동해서hasOwnProperty
메서드를 검색합니다. - 존재하지 않으므로 프로토타입 체인을 따라서
[[Prototype]]
에 바인딩 되어 있는 프로토타입인Object.prototype
으로 이동해서hasOwnProperty
메서드를 검색합니다. - 존재하므로
Object.prototype.hasOwnProperty
메서드를 호출합니다. 이때,Object.prototype.hasOwnProperty
메서드의this
에는tom
이 바인딩 됩니다.
프로토타입 체인의 종점
프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype
입니다.
따라서 모든 객체는 Object.prototype
를 상속받습니다.
Object.prototype
를 프로토타입 체인의 종점이라고 부릅니다.
Object.prototype
의 [[Prototype]]
내부 슬롯의 값은 null
입니다.
프로토타입 체인 종점인 Object.prototype
에서도 프로퍼티를 찾을 수 없는 경우에는 undefined
를 반환하기때문에 에러가 발생하지 않습니다.
mdn과 모던 자바스크립트 딥다이브를 공부하며 정리한 글입니다.
'JavaScript' 카테고리의 다른 글
[JavaScript] 클래스 (Class) (0) | 2023.03.15 |
---|---|
[JavaScript] 클로저 (Closure) (0) | 2023.03.15 |
[JavaScript] 원시 자료형과 참조 자료형 (0) | 2023.03.15 |
[JavaScript] if와 else if에서의 문제 해결 (0) | 2023.03.15 |
[JavaScript] 조건문과 반복문 (0) | 2023.03.15 |