
객체지향 프로그래밍의 핵심 개념으로, 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것.
// 생성자 함수
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
return Math.PI * this.radius ** 2;
}
}
const circle1 = new Cirecle(1);
const circle2 = new Cirecle(2);
// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유함.
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직함.
console.log(circle1.getArea === circle2.getArea); // false
JS는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거.
// 생성자 함수
function Circle(radius) {
this.radius = radius;
}
// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩 되어있음.
Circle.prototype.getArea = function () {
return Math.PI * this.radius ** 2;
}
const circle1 = new Cirecle(1);
const circle2 = new Cirecle(2);
console.log(circle1.getArea === circle2.getArea); // true
__proto__ 접근자 프로퍼티를 통해 자신의 프로토타입에 간접적으로 접근 가능.constructor 프로퍼티를 통해 생성자 함수에 접근할 수 있고, 생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입에 접근할 수 있음.[1. 생성자 함수 (Person)]
|
| (prototype 프로퍼티: "내 파트너는 쟤야")
| ➡️ 참조값 저장
|
v
[2. 프로토타입 객체 (Person.prototype)]
|
| (constructor 프로퍼티: "나를 만든 건 쟤야")
| ➡️ 참조값 저장 (역참조)
|
^
|
| (__proto__ 접근자: "내 부모님은 쟤야")
| ➡️ 참조값 매핑 (상속)
|
[3. 인스턴스 (me)]부모 객체.prototype을 참조하게 됨.prototype 객체임.__proto__ 접근자 프로퍼티__proto__ 접근자 프로퍼티를 통해 자신의 프로토타입 객체, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있음.__proto__ 접근자 프로퍼티다.접근자 프로퍼티는 자체적으로 값([[Value]] 프로퍼티 어트리뷰트)를 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수, [[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티임.
__proto__ 프로퍼티를 통해 프로토타입에 접근하면 내부적으로 [[Get]]이 호출, 새로운 프로토타입을 할당하면, [[Set]]이 호출됨.
const obj = {};
const parent = { x: 1 };
obj.__proto__;
obj.__proto__ = parent;
console.log(obj.x); // 1;
__proto__ 접근자 프로퍼티는 상속을 통해 사용됨.__proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티임.
모든 객체는 상속을 통해 Object.prototype.__proto__ 프로퍼티를 사용할 수 있음.
const person = { name: 'Lee' };
// person 객체는 직접적으로 __proto__ 프로퍼티를 소유하지 않음.
console.log(person.hasOwnProperty('__proto__')); // false
// 모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속 받아 사용.
console.log({}.__proto__ === Object.prototype); // true
console.log({}.__proto__ === person.__proto__); // true
__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서임.
const parent = {};
const child = {};
child.__proto__ = parent;
parent.__proto__ = child; // TypeError: Cyclic __proto__ value
위 코드 처럼 서로가 자신의 프로토타입이 되는 비정상적인 프로토타입 체인(순환 참조)이 만들어지기 전 __proto__ 접근자 프로퍼티는 에러를 발생시킴.
__proto__ 접근자 프로퍼티를 코드 내에 직접 사용하는 것은 권장하지 않음.Object.getPrototypeOf 메서드와 Object.setPrototypeOf 메서드를 사용하는 것을 권장.
// 상속 받는 곳이 없는 순수 객체 생성
const obj = Object.create(null);
console.log(obj.__proto__); // undefined
const obj = {};
const parent = { x: 1 };
Object.getPrototypeOf(obj); // obj.__proto
Object.setPrototypeOf(obj, parent) // obj.__proto__ = parent
console.log(obj); // 1
모든 프로토타입 객체는 constructor 프로퍼티를 갖음.
자신을 참조하고 있는 생성자 함수를 가리키려고.
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Kim');
// me 객체의 생성자 함수는 Person
console.log(me.constructor === Person); // true
const member1 = new Person('철수');
// member1이 뭘로 만들어졌는지 모르겠지만,
// 걔를 만든 기계(constructor)를 가져와서 영희를 또 찍어내자!
const member2 = new member1.constructor('영희');
console.log(member2.name); // '영희'
console.log(member2 instanceof Person); // true
책에서 겁나 어렵게 써있는데... JS엔진은 추상 연산을 통해 그에 맞는 생성자 함수와 프로토타입을 지정해 준다로 알고 넘어가면 될듯함.
| 리터럴 표기법 | 내부적으로 연결되는 생성자 함수 | 상속받는 프로토타입 |
|---|---|---|
객체 {} | Object | Object.prototype |
함수 function() {} | Function | Function.prototype |
배열 [] | Array | Array.prototype |
정규표현식 /abc/ | RegExp | RegExp.prototype |
리터럴로 생성해도 생성자 함수와 같이 constructor와 __proto__가 연결됨.
// 1. 객체 리터럴
const obj = {};
console.log(obj.constructor === Object); // true
console.log(obj.__proto__ === Object.prototype); // true
// 2. 배열 리터럴
const arr = [1, 2, 3];
console.log(arr.constructor === Array); // true
console.log(arr.__proto__ === Array.prototype); // true
// 3. 함수 리터럴
const func = function() {};
console.log(func.constructor === Function); // true
console.log(func.__proto__ === Function.prototype); // true
프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재.
new 연산자와 함께 생성자 함수로 호출 가능.constructor를 프로퍼티로 갖는 프로토타입 객체도 더불어 생성됨.프로토타입 객체는 constructor 프로퍼티만을 갖는 객체이며, 생성된 프로토타입의 프로토타입은 Object.prototype임.prototype 프로퍼티에 바인딩되 있음.OrdinaryObjectCreate에 의해 생성된다는 공통점이 있음.Object.prototype 임.const Person = (function() {
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`)
};
// 생성자 함수를 반환
return Person;
}());
const me = new Person('Kim');
// 인스턴스 메서드
me.sayHello = function () {
console.log(`Hey! My name is ${this.name}`);
};
// 인스턴스 메서드가 호출됨. 프로토타입 메서드는 인스턴스 메서드에 의해 가려짐.
me.sayHello(); // Hey! My name is Kim
// 인스턴스 메서드 삭제
delete me.sayHello;
me.sayHello(); // Hi! My name is Kim
// 프로토타입 체인을 통해 프로토타입 메서드는 삭제되지 않음
delete me.sayHello;
me.sayHello(); // Hi! My name is Kim
// 프로토타입 메서드 변경
Person.prototype.sayHello = function () {
console.log(`Hey! My name is ${this.name}`);
};
me.sayHello(); // Hey! My name is Lee
// 프로토타입 메서드 삭제
delete Person.prototype.sayHello;
me.sayHello(); // TypeError: me.sayHello is not a function

객체 instanceof 생성자 함수
__proto__ 접근자로 타고 올라가 접근이 가능한지)를 판별하는 거라고 보면 됨.첫 번째 인자: 부모가 될 객체 (필수)
두 번째 인자: 태어날 객체의 속성 정의 (선택, 좀 복잡함)
// 부모가 될 객체
const myProto = { x: 10 };
// 1. "myProto를 부모로 삼는 객체 obj를 만들어라!"
// (두 번째 인자는 생략 가능하지만, 쓴다면 이렇게 디스크립터로 써야 함)
const obj = Object.create(myProto, {
y: { value: 20, writable: true, enumerable: true, configurable: true }
});
console.log(obj.x); // 10
console.log(obj.y); // 20
console.log(Object.getPrototypeOf(obj) === myProto); // true
가장 쉽고 직관적인 방법
const myProto = { x: 10 };
// "객체를 만드는데, 내 부모(__proto__)는 myProto로 설정할게."
const obj = {
y: 20,
__proto__: myProto // 👈 여기서 바로 상속 지정!
};
console.log(obj.x); // 10 (상속됨)
console.log(Object.getPrototypeOf(obj) === myProto); // true
생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말함.
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
// 정적 프로퍼티
Person.staticProps = 'static prop';
// 정적 메서드
Person.staticMethod = function () {
console.log('staticMethod');
}
const me = new Person('Kim');
// 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출
Person.staticMethod();
// 정적 프로퍼티/메서드는 인스턴스로는 참조/호출 불가
me.staticMethod(); // TypeError: me.staticMethod is not a function
생성자 함수가 생성한 인스턴스는 자신의 프로토타입 체인에 속한 객체의 프로퍼티/메서드에는 접근할 수 있지만, 정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니기 때문에 접근이 불가함.
key in object
// in 연산자 대신 Reflect.has 메서드를 사용할 수도 있음
Reflect.has(object, key);
true를 반환false를 반환 함.const person = {
name: 'Kim',
address: 'Seoul'
};
for (const key in person) {
console.log(key + ': ' + person[key]);
}
true인 것들만 순회하며 열거.Object.prototype.hasOwnPropery 메서드를 사용하여 조건부 확인 해야함.const person = {
name: 'Kim',
address: 'Seoul',
__proto__: { age: 20 }
};
console.log(Object.keys(person)); // ["name", "address"]
console.log(Object.values(person)); // ["Kim", "Seoul"]
console.log(Object.entries(person)); // [["name", "Kim"], ["address", "Seoul"]]