객체지향 프로그래밍
객체지향 프로그래밍 : 여러 개의 독립적인 단위, 즉 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임
객체지향 프로그래밍은 실세계의 실체 (사물이나 개념)를 인식하는 철학적 사고를 프로그래밍에 접목하려는 시도에서 시작
실체는 특징이나 성질을 나타내는 속성을 가짐 → 실체를 인식, 구별 가능
추상화 : 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것
객체 : 상태 데이터와 동작을 하나의 논리적 단위로 묶은 복합적인 자료구조
- 프로퍼티 : 객체의 상태를 나타내는 데이터
- 메서드 : 상태 데이터를 조작할 수 있는 동작
상속과 프로토타입
상속 : 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것
자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복 제거 ( 기존의 코드 적극적 재사용 )
// 생성자 함수
function Circle (radius){
this.radius = radius;
this.getArea = function(){
return Math.PI * this.radius ** 2;
}
}
// 반지름이 1인 인스턴스 생성
const circle1 = new Circle(1);
// 반지름이 2인 인스턴스 생성
const circle1 = new Circle(2);
// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직
console.log(circle1.getArea === circle2.getArea); //false
console.log(circle1.getArea()); // 3.14159...
console.log(circle2.getArea()); // 12.5663...
동일한 생성자 함수에 의해 생성된 모든 인스턴스가 동일한 메서드를 중복 소유하는 것은 메모리를 불필요하게 낭비
→ 상속을 통해 중복 제거 필요 (프로토타입을 기반으로 상속 구현)
// 생성자 함수
function Circle(radius){
this.radius = radius;
}
// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가
// 프로토타입은 Circle생성자 함수의 prototype 프로퍼티에 바인딩되어 있음
Circle.prototype.getArea = function(){
return Math.PI * this.radius ** 2;
}
// 인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);
// Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는
// 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받음
// 즉 Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유
console.log(circle1.getArea === circle2.getArea); // true

프로토타입 객체
모든 객체는 [[Prototype]] 이라는 내부 슬롯을 가지며 이 내부 슬롯의 값은 프로토타입의 참조
객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]] 에 저장됨
- 객체 리터럴에 의해 생성된 객체의 프로토타입 = Object.prototype
- 생성자 함수에 의해 생성된 객체의 프로토타입 = 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체

내부 슬롯에는 직접 접근할 수 없지만 __proto__ 접근자 프로퍼티를 통해 자신의 [[Prototype]] 내부 슬롯이 가리키는 프로토타입에 간접적으로 접근 가능
프로토타입은 자신의 constructor 프로퍼티를 통해 생성자 함수에 접근 가능
생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입에 접근 가능
1. __proto__ 접근자 프로퍼티
모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입 [[Prototype]] 내부 슬롯에 간접적으로 접근 가능

1-1. __proto__는 접근자 프로퍼티다
[[Prototype]] 내부 슬롯에 직접 접근할 수 없으며 __proto__ 접근자 프로퍼티를 통해 간접적으로 프로토타입에 접근 가능
접근자 프로퍼티는 값 [[Value]]를 갖지 않고 프로퍼티 값을 읽거나 저장할 때 사용하는 접근자 함수([[Get]], [[Set]]) 어트리뷰트로 구성

Object.prototype의 접근자 프로퍼티 __proto__
- getter/ setter 함수라고 부르는 접근자 함수를 통해 프로토타입을 취득하거나 할당
- __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하면 내부적으로 내부 함수 getter 함수가 호출
- __proto__ 접근자 프로퍼티를 통해 새로운 프로토타입을 할당하면 내부적으로 내부 함수 setter 함수가 호출
const obj = {};
const parent = { x: 1 };
// __proto__ 접근자 프로퍼티의 getter 접근자 함수로 obj 객체의 프로토타입 객체 취득
console.log(obj.__proto__); // [Object: null prototype] {}
// __proto__ 접근자 프로퍼티의 setter 접근자 함수로 obj 객체의 프로토타입에 값 할당
obj.__proto__ = parent;
console.log(obj.__proto__); // { x: 1 }
1-2. __proto__ 접근자 프로퍼티는 상속을 통해 사용된다
- __proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype 의 프로퍼티
- 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티 사용 가능
const obj = {};
// obj 객체는 __proto__ 접근자 프로퍼티를 직접 소유하지 않음
console.log(obj.hasOwnProperty("__proto__")); // false
// __proto__ 접근자 프로퍼티는 모든 객체의 프로토타입 객체인 Object.protoype의 접근자 프로퍼티
console.log(Object.getOwnPropertyDescriptor(Object.prototype, "__proto__"));
/*
{
{enumerable: false, configurable: true, get: ƒ, set: ƒ}
configurable: true
enumerable: false
get: ƒ __proto__()
set: ƒ __proto__()
}
*/
// 모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용
console.log(obj.__proto__ === Object.prototype); // true
1-3. __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위하여
- 프로토타입 체인은 단방향 연결 리스트로 구현되어야 함 ( 프로퍼티 검색 방향이 한쪽 방향으로만 흘러가야함)
const parent = {};
const child = {};
child.__proto__ = parent;
parent.__proto__ = child; // TypeError: Cyclic __proto__ value

순환참조하는 프로토타입 체인이 만들어지면 프로토타입 체인 종점이 존재하지 않기 때문에 무한루프에 빠짐
→ 아무런 체크 없이 무조건적으로 프로토타입을 교체할 수 없도록 __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현
1-4. __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다
모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문
Object.prototype 을 상속받지 않는 객체를 생성할 수도 있기 때문
// obj는 프로토타입 체인의 종접이기 때문에 Object.__proto__ 를 상속받을 수 없음
const obj = Object.create(null);
// obj는 Object.__proto__ 를 상속받을 수 없음
console.log(obj.__proto__); // undefined
// Object.getPrototypeOf 메서드를 사용하는 편이 좋음
console.log(Object.getPrototypeOf(obj)) // null
__proto__ 접근자 프로퍼티 대신 프로토타입 참조를 취득하고 싶을 때 Object.getPrototypeOf 메서드 사용
프로토타입을 교체하고 싶은 경우에는 Object.setPrototype 메서드 사용 권장
const obj = {};
const parent = { x: 1 };
// obj 객체의 프로토타입 취득
console.log(Object.getPrototypeOf(obj)); // [Object: null prototype] {}
// obj 객체의 프로토타입을 교체
Object.setPrototypeOf(obj, parent);
console.log(obj.x); // 1
2. 함수 객체의 prototype 프로퍼티
함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴
// 함수 객체는 protoype 프로퍼티를 소유
console.log(function () {}.hasOwnProperty("prototype")); // true
// 일반 객체는 prototype 프로퍼티를 소유 X
console.log({}.hasOwnProperty("prototype")); // false
생성자 함수로서 호출할 수 없는 함수 (non-constructor)인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유 및 생성하지 않음
모든 객체가 가지고 있는 (Object.prototype 으로 부터 상속받은) __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킴
| 구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
| __proto__ 접근자 프로퍼티 | 모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
| prototype 프로퍼티 | constructor | 생성자 함수 | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |
function Person(name) {
this.name = name;
}
const me = new Person("Echichi");
// prototype 프로퍼티와 __proto__ 접근자 프로퍼티가 가리키는 것은 동일한 프로토타입
console.log(me.__proto__ === Person.prototype); // true

3. 프로토타입의 constructor 프로퍼티와 생성자 함수
모든 프로토타입은 constructor 프로퍼티를 가짐 → 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킴 (이 연결은 생성자 함수가 생성될 때 즉, 함수 객체가 생성될 때 이루어짐)
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person("Echichi");
// me 객체의 constructor 프로퍼티 = prototype 프로퍼티 = 자신을 참조하고 있는 생성자 함수 (Person)
console.log(me.constructor === Person); // true
me 객체에는 constructor 프로퍼티가 없지만 me 객체의 프로토타입인 Person.prototype 에는 constructor 프로퍼티가 있음
→ me 객체는 프로토타입은 Person.prototype의 constructor 프로퍼티를 상속받아 사용 가능
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없음
// 객체 리터럴로 obj 객체 생성
const obj = {};
// obj 객체의 생성자 함수는 Object 생성자 함수다.
console.log(obj.constructor === Object); // true
ECMAScript Objcet 생성자 함수 구현 방식
Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로는 추상연상 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체 생성
추상 연산 : ECMAScript 사양 내부 동작의 구현 알고리즘 표현 (설명을 위해 사용되는 함수와 유사한 의사 코드)
// 2. Object 생성자 함수에 의한 객체 생성
// 인수가 전달되지 않았을 때 추상연산 OrdinaryObjectCreate를 호출하여 빈 객체 생성
let obj = new Object();
console.log(obj); // {}
1. new.target 이 undefined나 Object가 아닌 경우
// 인스턴스 → Foo.prototype → Object.prototype 순으로 프로토타입 체인 생성
class Foo extends Objcet{}
new Foo(); // Foo{}
// 3. 인수가 전달된 경우에는 인수를 객체로 변환
obj = new Object(123);
console.log(obj); // Number{1,2,3}
객체 리터럴이 평가될 때는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의
생성자 함수 호출과 객체 리터럴의 평가는 추상연산 OrdinaryObjectCreate 를 호출하여 빈 객체를 생성하는 점에서 동일하나 new.target의 확인이나 프로퍼티를 추가하는 처리 등 세부 내용은 다름
→ 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님
// foo 함수는 Function 생성자 함수로 생성한 함수 객체가 아니라 함수 선언문으로 생성
function foo (){}
// 하지만 constructor 프로퍼티를 통해 확인해보면 foo의 생성자 함수는 Function 생성자 함수
console.log(foo.constructor === Function) // true
리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요하기 때문에 가상의 생성자 함수를 가짐
프로토타입은 생성자 함수와 더불어 생성 → prototype, constructor 프로퍼티에 의해 연결 → 프로토타입과 생성자 함수는 언제나 쌍으로 존재
| 리터럴 표기법 | 생성자 함수 | 프로토타입 |
| 객체 리터럴 | Object | Object.prototype |
| 함수 리터럴 | Function | Function.prototype |
| 배열 리터럴 | Array | Array.prototype |
| 정규 표현식 리터럴 | RegExp | RegExp.prototype |
프로토타입의 생성 시점
프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨
프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재하기 때문
1. 사용자 정의 생성자 함수와 프로토타입 생성 시점
생성자 함수로서 호출할 수 있는 함수 즉 constructor 는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됨
// 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입 더불어 생성
console.log(Person.prototype); // { constructor: f }
// 생성자 함수
function Person(name) {
this.name = name;
}
생성된 프로토타입은 오직 constructor 프로퍼티만 가짐
프로토타입 = 객체 → 모든 객체는 프로토타입을 가짐 → 프로토타입도 자신의 프로토타입을 가짐 → 생성된 프로토타입의 프로토타입은 (Object.prototype)
2. 빌트인 생성자 함수와 프로토타입 생성 시점
빌트인 생성자 함수가 생성되는 시점에 프로토타입 생성
모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성 → 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩
객체가 생성되기 이전 생성자 함수와 프로토타입은 이미 객체화되어 존재 → 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당됨 →생성된 객체는 프로토타입 상속받음
객체 생성 방식과 프로토타입의 결정
객체 생성 방법
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- Object.create 메서드
- 클래스 (ES6)
추상연산 OrdinaryObjectCreate은 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달 받음 → (자신이 생성할 객체에 추가할 프로퍼티 목록을 옵션으로 전달 가능) → OrdinartObjectCreate는 빈 객체를 생성 → 객체에 추가할 프로퍼티 목록이 인수로 전달된 경우 프로퍼티를 객체에 추가 → 인수로 전달받은 프로토타입을 자신이 생성한 객체의 [[Prototype]] 내부 슬롯에 할당 → 생성한 객체 반환
1. 객체 리터럴에 의해 생성된 객체의 프로토타입
객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype
// 객체 리터럴로 생성
const obj = { x : 1 };
// Object.prototype을 프로토타입으로 가지며 Object.prototype을 constructor로 상속받음
console.log(obj.constructor === Object) // true
// obj 객체는 constructor 프로퍼티와 hasOwnProperty 가 없지만
// 자신의 프로토타입인 Object.prototype의 constructor 프로퍼티와 hasOwnProperty를 상속받았기 때문에 사용 가능
console.log(obj.hasOwnProperty('x')) // true
2. Object 생성자 함수에 의해 생성된 객체의 프로토타입
Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype
// Object 생성자 함수로 생성
const obj = new Object();
obj.x = 1;
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty("x")); // true
3. 생성자 함수에 의해 생성된 객체의 프로토타입
생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩 되어있는 객체
사용자 정의 생성자 함수와 더불어 생성된 프로토타입 생성자.prototype의 프로퍼티는 constructor 뿐임
// 사용자 정의 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 프로퍼티 동적으로 추가
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
// 사용자 정의 생성자 함수로 생성된 인스턴스
const me = new Person("Echichi");
const you = new Person("Achichi");
me.sayHello(); // Hi! My name is Echichi
you.sayHello(); // Hi! My name is Achichi
console.log(me.constructor === Person); // true
console.log(you.constructor === Person); // true

프로토타입 체인
프로토타입 체인 : 객체의 프로퍼티 (메서드 포함) 에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색
function Person(name) {
this.name = name;
}
const me = new Person("Echichi");
// hasOwnProperty 는 Object.prototype 의 메서드
// me 객체는 프로토타입 체인을 따라 hasOwnProperty 메서드를 검색하여 사용
console.log(me.hasOwnProperty("name")); // true
1. hasOwnProperty 메서드를 호출한 me 객체에서 hasOwnProperty 메서드 검색
→ hasOwnProperty 메서드가 없으므로 프로토타입 체인에 따라 [[Prototype]] 내부 슬롯에 바인딩 되어 있는 프로토타입(Person. prototype) 으로 이동하여 hasOwnProperty 메서드 검색
2. Person.prototype 에도 Person.prototype 가 없으므로 프로토타입 체인에 따라 [[Prototype]] 내부 슬롯에 바인딩 되어 있는 프로. 토타입(Object.prototype) 으로 이동하여 hasOwnProperty 메서드 검색
3. Object.prototype 에는 hasOwnProperty가 존재하므로 호출
(Object.prototype.hasOwnProperty 메서드의 this에 me 객체 바인딩)
프로토타입 체인
- Object.prototype 은 프로토타입의 종점 (모든 객체는 Object.prototype 을 상속받기 때문 → Object.prototype 의 프로토타입 [[Prototype]] 내부 슬롯의 값은 null
- 프로토타입 체인은 상속과 프로퍼티 검색을 위한 매커니즘
스코프 체인
- 프로퍼티가 아닌 식별자는 스코프 체인에서 검색
- 스코프 체인은 식별자 검색을 위한 매커니즘
스코프체인과 프로토타입 체인은 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용 (스코프 체인으로 me 식별자를 검색한 다음 me 객체의 프로토타입 체인에서 hasOwnProperty 메서드 검색)
오버라이딩과 프로퍼티 섀도잉
프로토타입 프로퍼티 : 프로토타입이 소유한 프로퍼티 (메서드 포함)
인스턴스 프로퍼티 : 인스턴스가 소유한 프로퍼티 (메서드 포함)
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("Echichi");
// 인스턴스 메서드
me.sayHello = function () {
console.log(`Hey! My name is ${this.name}`);
};
// 인스턴스 메서드 호출
// 프로토타입 메서드는 인스턴스 메서드에 의해 가려짐 : 프로퍼티 섀도잉
console.log(me.sayHello()); // Hey! My name is Echichi

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 인스턴스 프로퍼티로 추가
인스턴스 메서드는 프로토타입 메서드를 오버라이딩 했고 프로토타입 메서드는 가려짐
프로퍼티 섀도잉 : 상속 관계에 의해 프로퍼티가 가려지는 현상
| 오버라이딩 | 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식 |
| 오버로딩 | 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식 (자바스크립트는 오버로딩을 지원하지 않지만 arguments 객체를 사용하여 구현 가능) |
프로토타입의 프로퍼티를 변경 또는 삭제하려면 하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아니라 프로퍼티에 직접 접근해야함
// 인스턴스 메서드 삭제
delete me.sayHello;
// 인스턴스에는 sayHello 가 없으므로 프로토타입 메서드 호출
me.sayHello(); // Hi! My name is Echichi
// 프로토타입 체인을 통해 프로토타입 메서드 삭제 불가
delete me.sayHello;
// 프로토타입 메서드 호출
me.sayHello(); // Hi! My name is Echichi
// 프로토타입 메서드 삭제
delete Person.prototype.sayHello;
me.sayHello(); // TypeError
프로토타입의 교체
프로토타입은 생성자 함수 또는 인스턴스에 의해 교체 가능 (부모 객체인 프로토타입을 동적으로 변경 가능)
1. 생성자 함수에 의한 프로토타입 교체
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
sayHello() {
console.log(`Hi, My name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Echichi");
// 생성자 함수에 프로퍼티로 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype 의 constructor 프로퍼티가 검색
console.log(me.constructor === Object); // true
파괴된 constructor 프로퍼티와 생성자 함수 간의 연결 되살리기
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결 설정
constructor: Person,
sayHello() {
console.log(`Hi, My name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Echichi");
// constructor 프로퍼티와 생성자 함수 간의 연결 설정으로 인해 constructor 프로퍼티가 생성자 함수를 가리킴
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false
2. 인스턴스에 의한 프로토타입의 교체
프로토타입은 인스턴스의 __proto__ 접근자 프로퍼티 (또는 Object.getPrototypeOf 메서드)를 통해 프로토타입 교체 가능
생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩 한다는 것 : 미래에 생성할 인스턴스의 프로토타입 교체
__proto__ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것 : 이미 생성된 객체의 프로토타입 교체
function Person(name){
this.name = name;
}
const me = new Person('Echichi');
// 프로토타입으로 교체할 객체
const parent = {
sayHello(){
conosle.log(`Hi! My name is ${this.name}`);
}
};
// me 객체의 프로토타입을 parent 객체로 교체
Object.setPrototypeOf(me , parent);
// = me.__proto__ = parent;
me.sayHello(); // Hi! My name is Echichi
// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결 파괴
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype 의 constructor 프로퍼티 생성
console.log(me.constructor === Object); // false


파괴된 생성자 함수와 프로토타입 간의 연결 되살리기
function Person(name){
this.name = name;
}
const me = new Person('Echichi');
// 프로토타입으로 교체할 객체
const parent = {
// construcotr 프로퍼티와 생성자 함수 간의 연결 설정
constructor : Person,
sayHello(){
conosle.log(`Hi! My name is ${this.name}`);
}
};
// 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결 설정
Person.prototype = parent;
// me 객체의 프로토타입을 parent 객체로 교체
Object.setPrototypeOf(me , parent);
// = me.__proto__ = parent;
me.sayHello(); // Hi! My name is Echichi
// constructor 프로퍼티가 생성자 함수를 가리킴
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false
// 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킴
console.log(Person.prototype === Object.getPrototypeOf(me)) // true
instance of 연산자
객체 instanceof 생성자 함수
생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인
true : 우변의 생성자 함수의 prototype 에 바인딩 된 객체가 좌변의 객체의 프로토타입 체인 상에 존재
false : 우변의 생성자 함수의 prototype 에 바인딩 된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하지 않음
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person("Echichi");
// 프로토타입으로 교체할 객체
const parent = {};
Object.setPrototypeOf(me, parent);
// Person 생성자 함수와 parent 객체는 연결되어 있지 않다.
console.log(Person.prototype === parent); // false
console.log(parent.constructor === Person); // false
// parent 객체를 Person 생성자 함수의 prototype 프로퍼티에 바인딩
Person.prototype = parent;
// Person.prototype 이 me 객체의 프로토타입 체인 상에 존재
console.log(me instanceof Person); // true
// Object.prototype 이 me 객체의 프로토타입 체인 상에 존재
console.log(me instanceof Object); // true
instanceof 연산자 함수로 표현
function isInstanceof(instance, constructor){
// 프로토타입 취득
const prototype = Object.getPrototypeOf(instance);
// 재귀 탈출 조건
// prototype 이 null 이면 프로토타입 체인의 종점에 다다른 것
if(prototype === null) return false;
// 프로토타입이 생성자 함수의 prototype 프로퍼티에 바인딩 된 객체라면 true 반화
// 그렇지 않다면 재귀 호출로 프로토타입 체인 상의 프로토타입으로 이동하여 확인
return prototype === constructor.prototype || isInstanceof(prototype, constructor);
}
직접 상속
1. Object.create 에 의한 직접 상속
Object.create는 명시적으로 프로토타입을 지정하여 새로운 객체 생성 (다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 호출)
Object.create(생성할 객체의 프로토타입으로 지정할 객체, 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체)
// obj -> null
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); // true
// obj -> Object.prototype -> null
// obj = {}; 와 동일
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// obj -> Objrct.prototype -> null
// obj = { x : 1}; 와 동일
obj = Object.create(Object.prototype, {
x: { value: 1, writable: true, enumerable: true, configurable: true },
});
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
const myProto = { x: 10 };
// obj -> myProto -> Object.prototype -> null
obj = Object.create(myProto);
console.log(Object.getPrototypeOf(obj) === myProto); // true
// 생성자 함수
function Person(name) {
this.name = name;
}
// obj -> Perosn.prototype -> Object.prototype -> null
// obj = new Person('Echichi')와 동일
obj = Object.create(Person.prototype);
obj.name = 'Echichi';
console.log(Object.getPrototypeOf(obj) === Person.prototype); // true
객체를 생성하면서 직접적으로 상속을 구현
Object.create의 장점
- new 연산자가 없이도 객체 생성 가능
- 프로토타입을 지정하면서 객체 생성 가능
- 객체 리터럴에 의해 생성된 객체도 상속 받을 수 있음
// 프로토타입 체인 종점에 위치하는 객체의 프로토타입은 null
// 프로토타입이 null인 객체 즉 프로토타입 체인의 종점에 위치하는 객체 생성
const obj = Object.create(null);
obj.a = 1;
console.log(Object.getPrototype(obj) === null); //true
// obj는 Object.prototype 의 빌트인 메서드를 사용할 수 없다
console.log(obj.hasOwnProperty('a')); // TypeError
// Object.prototype 의 빌트인 메서드는 객체로 직접 호출하지 않는다.
console.log(Object.prototype.hasOwnProperty.call(obj, "a")); // true
ESLint 에서는 Object.prototype 의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않음
→ Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성 가능하기 때문
→ 프로토타입 체인 종점에 위치하는 객체는 Object.prototype 의 빌트인 메서드 사용 불가
2. 객체 리터럴 내부에서 __proto__ 에 의한 직접 상속
const myProto = { x: 10 };
// 객체 리터럴에 의해 객체를 생성하면서, 프로토타입을 지정하여 직접 상속을 구현할 수 있다.
const obj = {
y: 20,
// obj -> myProto -> Object.prototype -> null
__proto__: myProto,
};
/*
위 코드는 아래와 동일
const obj = Object.create(myProto, {
y: { value: 20, writable: true, enumerable: true, configurable: true }
})
*/
console.log(obj.x, obj.y); // 10 20
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.staticProp = "static prop";
// 정적 메서드
Person.staticMethod = function () {
console.log("staticMethod");
};
const me = new Person("Echichi");
// 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출
Person.staticMethod(); // staticMethod
// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출 불가
// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야함
me.staticMethod(); // TypeError: me.staticMethod is not a function

정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니라 인스턴스로 접근 불가
function foo (){}
// 프로토타입 메서드
// this 를 참조하지 않는 프로토타입 메서드는 정적 메서드로 변경하여도 동일한 효과를 얻을 수 있다
Foo.prototype.x = function(){
console.log('x');
};
const foo = new Foo();
// 프로토타입 메서드를 호출하려면 인스턴스를 생성해야한다
foo.x(); // x
// 정적 메서드
Foo.x = function(){
console.log('x');
};
// 정적 메서드는 인스턴스를 생성하지 않아도 호출 가능
Foo.x(); // x
인스턴스/ 프로토타입 메서드 내에서 this 를 사용하지 않는다면 그 메서드는 정적 메서드로 변경 가능 (메서드 내 this = 인스턴스)
프로토타입 메서드를 호출하려면 인스턴스를 생성해야 하지만 정적 메서드는 인스턴스를 생성하지 않아도 호출 가능
프로퍼티 존재 확인
1. in 연산자
in 연산자 : 객체 내 특정 프로퍼티가 존재하는지 여부 확인
// key : 프로퍼티 키를 나타내는 문자열
// object : 객체로 평가되는 표현식
key in object;
in연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인
const person = {
name: "Echichi",
age: 100,
};
console.log("name" in person); // true
console.log("age" in person); // true
// Person 객체에 address 프로퍼티가 존재하지 않는다
console.log("address" in person); // false
person 객체의 프로토타입인 Object.prototype 에 toString 메서드가 존재하기 때문에 true
console.log("toString" in person); // true
Reflect.has 메서드 : in 연산자와 동일하게 동작
const Person = { name : 'Echichi'};
console.log(Reflect.has(Person, 'name')); // true
console.log(Reflect.has(Person, 'toString')); // true
2. ObjectPrototype.hasOwnProperty 메서드
ObjectPrototype.hasOwnProperty 메서드 : 객체에 특정 프로퍼티가 존재하는지 확인
in 연산자와 다르게 객체 고유의 프로퍼티 키인 경우에만 true 상속받은 프로퍼티 타입의 프로퍼티 키인 경우 false 반환
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("age")); // false
console.log(person.hasOwnProperty("toString")); // false
프로퍼티 열거
1. for ... in 문
for...in 문 : 객체의 모든 프로퍼티를 순회하며 열거할 때 사용
for(변수 선언문 in 객체) {...}
- for..in 연산자는 in 연산자처럼 상속받은 프로토타입의 프로퍼티까지 열거
- Object.prototype 프로퍼티는 열거되지 않음 ([[Enumerable]] 값이 false이기 때문 → 열거 불가능)
- 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음
- 순서 보장 X
for...in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]] 의 값이 true 인 프로퍼티를 순회하며 열거
const sym = Symbol();
const person = {
name: "Echichi",
age: 100,
[sym] : 1030,
__proto__: {
address: "Daejeon",
},
};
// in 연산자는 객체가 상속받은 모든 프로토타입의 프로퍼티 확인
console.log("toString" in person); // true
// toString같은 Object.prototype 프로퍼티가 열거되지 않음
for (const key in person) {
console.log(`${key} : ${person[key]}`);
}
/*
name : Echichi
age : 100
address : Daejeon
*/
상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnProperty 메서드를 사용
const person = {
name: "Echichi",
age: 100,
__proto__: {
address: "Daejeon",
},
};
for (const key in person) {
// 객체 자신의 프로퍼티인지 확인
if (person.hasOwnProperty(key)) {
console.log(`${key} : ${person[key]}`);
}
}
/*
name : Echichi
age : 100
*/
2. Object.keys/ values/ entries 메서드
Object.keys/ values/ entries 메서드 : 객체 자신의 고유 프로퍼티만 열거
- Object.keys 메서드 : 객체 자신의 열거가능한 프로퍼티 키를 배열로 반환
- Object.values 메서드 : 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환
- Object.entries 메서드 : 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
const person = {
name: "Echichi",
age: 100,
__proto__: {
address: "Daejeon",
},
};
console.log(Object.keys(person)); // [ 'name', 'age' ]
console.log(Object.values(person)); // [ 'Echichi', 100 ]
console.log(Object.entries(person)); // [ [ 'name', 'Echichi' ], [ 'age', 100 ] ]'Javascript' 카테고리의 다른 글
| [모던 자바스크립트 Deep Dive] 18. 빌트인 객체 (0) | 2023.11.14 |
|---|---|
| [모던 자바스크립트 Deep Dive] 17. strict mode (0) | 2023.11.13 |
| [모던 자바스크립트 Deep Dive] 15. 함수와 일급 객체 (0) | 2023.11.11 |
| [모던 자바스크립트 Deep Dive] 14. 생성자 함수에 의한 객체 생성 (0) | 2023.11.10 |
| [모던 자바스크립트 Deep Dive] 13. 프로퍼티 어트리뷰트 (0) | 2023.11.09 |