
- 자바스크립트는 클래스 기반 객체지향 프로그래밍 언어보다 효율적, 더 강력한 객체지향 프로그래밍 능력을 지니고 있는 프로토타입 기반의 객체지향 프로그래밍 언어.
19.1 객체지향 프로그래밍
- 전통적인 명령형 프로그래밍의 절차 지향적 관점에서 벗어나 객체의 집합으로 표현하려는 프로그래밍 패러다임.
• 속성 : 실세계의 실체(사물이나 개념)를 인식하는 철학적 사고를 프로그래밍에 접목하려는 시도에서 시작. 특징이나 성질 나타냄.
• 추상화 : 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것.
• 객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조
// 예제 19-02
const circle = {radius: 5, // 반지름
// 원의 지름: 2r
getDiameter() {
return 2 * this.radius;
},
// 원의 둘레 : 2nr
getPerimeter() {
return 2 * Math.PI * this.radius;
},
// 원의 넓이: nrr
getArea() {
return Math.PI * this.radius ** 2;
}
};
console.log(circle);
// {radius: 5, getDiameter: f, getPerimeter: f, getArea: f}
console.log(circle.getDiameter()); // 10
console.log(circle.getPerimeter()); // 31.41592653589793
console.log(circle.getArea());; // 78.53981633974483
- 반지름은 원의 상태를 나타내는 데이터이며 원의 지름, 둘레, 넓이를 구하는 것은 동작.
- 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어 생각.
- 객체의 상태 데이터를 프로퍼티 property, 동작을 메서드 method.
19.2 상속과 프로토타입
• 상속: 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것.
- 불필요한 중복을 제거
- 기존의 코드 재사용
- 개발 비용을 줄일 수 있음
// 예제 19-03
// 생성자 함수
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
// Math.PI는 원주율을 나타내는 상수다.
return Math.PI * this.radius ** 2;
};
}
// 반지름이 1인 인스턴스 생성
const circle1 = new Circle(1);
// 반지름이 2인 인스턴스 생성
const circle2 = new Circle(2);
// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직하다.
console.log(circle1.getArea === circle2.getArea); // false
console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

// 예제 19-04
// 생성자 함수
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
console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

19.3 프로토타입 객체
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조(null인 경우도 있다).
- [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정.
- 즉, 객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장.

19.3.1 __proto__ 접근자 프로퍼티
- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
__proto__는 접근자 프로퍼티다.
- __proto__ 접근자 프로퍼티를 통해 간접적으로 [[Prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근할 수 있다.
- 접근자 프로퍼티는 [[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티다.
- 접근자 프로퍼티의 getter 함수인 [[Get]]이 호출.
- __proto__ 접근자 프로퍼티를 통해 새로운 프로토타입을 할당하면 __proto__ 접근자 프로퍼티의 setter 함수인 [[Set]]이 호출된다.
// 예제 19-06
const obj = {};
const parent = { x: 1 };
// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;
// setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__protose = parent;
console.log(obj.x); // 1
__proto__접근자 프로퍼티는 상속을 통해 사용된다.
- __proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티다.
- 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티를 사용할 수 있다.
// 예제 19-07
const person = { name: 'Lee' };
// person 객체는 __proto__ 프로퍼티를 소유하지 않는다.
console.log(person.hasOwnProperty('__proto__')); // false
// __proto__ 프로퍼티는 모든 객체의 프로토타입 객체인 Object.prototype의 접근자 프로퍼티다.
console.log(object.getOwnPropertyDescriptor(object.prototype, '__proto__'));
// {get: f, set: f, enumerable: false, configurable: true}
// 모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용할 수 있다.
console.log({}.__proto__ === Object.prototype); // true
__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
- 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서 접근자 프로퍼티를 사용
// 예제 19-08
const parent = {};
const child = {};
// child의 프로토타입을 parent로 설정
child.__proto__ = parent
// parent의 프로토타입을 child로 설정
parent.__proto__ = child; // TypeError: Cyclic __proto__ value

- 프로토타입 체인은 단방향 링크드 리스트로 구현.
- 순환 참조하는 프로토타입 체인이 만들어지면 종점이 존재하지 않기 때문에 프로퍼티를 검색할 때 무한 루프에 빠짐.
__proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
- 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드를 사용,
- 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용할 것을 권장
19.3.2 함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
- 모든 객체가 가지고 있는 __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다.
| 구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
| __proto__ 접근자 프로퍼티 |
모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
| prototype 프로퍼티 |
constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |
// 예제 19-13
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// 결국 Person.prototype과 me.__proto__는 결국 동일한 프로토타입을 가리킨다.
console.log(Person.prototype === me.__proto__); // true
(19-7그림추가)
19.3.3 프로토타입의 constructor 프로퍼티와 생성자 함수
- 모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다. 이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이뤄진다.
(19-8그림추가)
19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재한다.
- 하지만 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없다.
// 예제 19-17
// obj 객체는 Object 생성자 함수로 생성한 객체가 아니라 객체 리터럴로 생성했다.
const obj = {};
// 하지만 obj 객체의 생성자 함수는 Object 생성자 함수다.
console.log(obj.constructor === Object); // true
- 2에서 Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로는 추상 연산 OrdinaryobjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 객체를 생성한다.
// 예제 19-18
// 2. Object 생성자 함수에 의한 객체 생성
// 인수가 전달되지 않았을 때 추상 연산 OrdinaryobjectCreate를 호출하여 빈 객체를 생성한다.
let obj = new Object();
console.log(obj); // {}
// 1. new.targeti undefined, Object가 아닌 경우
// 인스턴스 - Foo. prototype - Object.prototype 순으로 프로토타입 체인이 생성된다.
class Foo extends Object {}
new Foo(); // Foo {}
// 3. 인수가 전달된 경우에는 인수를 객체로 변환한다
// Number 객체 생성
obj = new Object(123);
console.log(obj); // Number {123}
// String 객체 생성
obj = new Object('123');
console.log(obj); // String {"123"}
- 객체 리터럴이 평가될 때는 다음과 같이 추상 연산 OrdinaryobjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의되어 있다.
- 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니다.
// 예제 19-19
// foo 함수는 Function 생성자 함수로 생성한 함수 객체가 아니라 함수 선언문으로 생성했다.
function foo() {}
// 하지만 constructor 프로퍼티를 통해 확인해보면 함수 foo의 생성자 함수는 Function 생성자 함수다.
console.log(foo.constructor === Function); // true
- 리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요하다.
- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.
| 리터럴 표기법 | 생성자 함수 | 프로토 타입 |
| 객체 리터럴 | Object | Object.prototype |
| 함수 리터럴 | Function | Function.prototype |
| 배열 리터럴 | Array | Array.prototype |
| 정규 표현식 리터럴 | RegExp | RegExp.prototype |
19.5 프로토타입의 생성 시점
- 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.
19.5.1 사용자 정의 생성자 함수와 프로토타입 생성 시점
- 생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
- 생성자 함수로서 호출할 수 없는 함수, 즉 non-constructor는 프로토타입이 생성되지 않는다.
- 생성된 프로토타입의 프로토타입은 Object.prototype이다.
(19-12그림추가)
19.5.2 빌트인 생성자 함수와 프로토타입 생성 시점
- 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다.
- 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩된다.
(19-13그림추가)
- 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당.
19.6 객체 생성 방식과 프로토타입의 결정
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- Object.create 메서드
- 클래스(ES6)
- 추상 연산 OrdinaryobjectCreate14에 의해 생성된다는 공통점.
- 프로토타입은 추상 연산 OrdinaryobjectCreate에 전달되는 인수에 의해 결정
19.6.1 객체 리터럴에 의해 생성된 객체의 프로토타입
- 객체 리터럴을 평가하여 객체를 생성할 때 추상 연산 OrdinaryobjectCreate를 호출
(19-14그림추가)
- obj 객체는 자신의 프로토타입인 object.prototype의 constructor 프로퍼티와 hasOwnProperty 메서드를 자신의 자산인 것처럼 자유롭게 사용할 수 있다.
- 이는 obj 객체가 자신의 프로토타입인 Object.prototype 객체를 상속받았기 때문이다.
19.6.2 Object 생성자 함수에 의해 생성된 객체의 프로토타입
- Object 생성자 함수를 인수 없이 호출하면 빈 객체가 생성된다.
- 추상 연산 OrdinaryobjectCreate가 호출.
- 객체 리터럴에 의해 생성된 객체와 동일한 구조를 갖는다.
(19-15그림추가)
- Object 생성자 함수 방식은 일단 빈 객체를 생성한 이후 프로퍼티를 추가해야 한다.
19.6.3 생성자 함수에 의해 생성된 객체의 프로토타입
- new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 추상 연산 OrdinaryobjectCreate 호출.
- 추상 연산 OrdinaryobjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
// 예제 19-27
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
(19-16그림추가)
// 예제 19-28
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person('Lee');
const you = new Person('Kim');
me.sayHello(); // Hi! My name is Lee
you.sayHello(); // Hi! My name is Kim
- Person 생성자 함수를 통해 생성된 모든 객체는 sayhello 메서드를 상속받아 자신의 메서드처럼 사용할 수 있다.
(19-17그림추가)
* 위 내용은 모던 자바스크립트 Deep Dive를 참고하여 개인적인 공부를 목적으로 정리하였습니다.
모던 자바스크립트 Deep Dive - 교보문고
자바스크립트의 기본 개념과 동작 원리 | 웹페이지의 단순한 보조 기능을 처리하기 위한 제한적인 용도로 태어난 자바스크립트는 과도하다고 느껴질 만큼 친절한 프로그래밍 언어입니다. 이러
www.kyobobook.co.kr
'Front-End > Javascript' 카테고리의 다른 글
| [JavaScript] ReferenceError: '' is not defined 해결방법 (0) | 2022.04.16 |
|---|---|
| [JavaScript] 19장 프로토 타입 (2) (0) | 2022.03.31 |
| [JavaScript] 18장 함수와 일급 객체 (0) | 2022.03.31 |
| [JavaScript] 17장 생성자 함수에 의한 객체 생성 (0) | 2022.03.31 |
| [JavaScript] 16장 프로퍼티 어트리뷰트 (0) | 2022.03.31 |