19.1 객체지향 프로그래밍
- 객체 지향 프로그래밍 : 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임
- 객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조
// 이름과 주소 속성을 갖는 객체
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log(person); // {name: "Lee", address: "Seoul"}
아래는 원이라는 개념을 객체로 표현해 본 것이다.
const circle = {
radius: 5, // 반지름
//원의 지름: 2r
getDiameter() {
return 2 * this.radius;
},
// 원의 둘레: 2πr
getPerimeter() {
return 2 * Math.PI * this.radius;
},
// 원의 넓이: πrr
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 상속과 프로토타입
- 상속(inheritance) : 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 의미
- 자바스크립트는 프로토타입 기반으로 상속 구현하여 불필요한 중복 제거
- 생성자 함수의 문제
: 동일한 생성자 함수에 의해 생성된 모든 인스턴스가 동일한 메서드를 중복 소유하는 것은 메모리를 불필요하게 낭비함
: 또한 인스턴스를 생성할 때마다 메서드를 생성하므로 퍼포먼스에도 악영향을 줌
: 만약 10개의 인스턴스를 생성하면 내용이 동일한 메서드도 10개 생성되는 것임
아래 예시는 프로토타입을 기반으로 상속 구현한 예시
//생성자 함수
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
└ Circle 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입,
즉 상위(부모)객체 역할을 하는 Circle,prototype의 모든 프로퍼티와 메서드를 상속받음
19.3 프로토타입 객체
- 프로토타입 객체
: 객체 간 상속을 구현하기 위해 사용
: 어떤 객체의 상위(부모)객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)를 제공
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조임
- [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정됨
└ 객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장
└ 객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototype
└ 생성자 함수에 의해 생성된 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체임
- 모든 객체는 하나의 프로토타입을 가지며, 모든 프로토타입은 생성자 함수와 연결되어 있음
[[Prototype]] 내부 슬롯에는 직접 접근할수 없지만, 위 그림처럼 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입,
즉 자신의 [[Prototype]] 내부 슬롯이 가리키는 프로토타입에 간접적으로 접근 가능
19.3.1 _ _proto_ _ 접근자 프로퍼티
- 모든 객체는 _ _proto_ _ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근 가능
( 1 ) _ _proto_ _는 접근자 프로퍼티
- 내부 슬롯은 프로퍼티가 아니기 때문에 자바스크립트는 원칙적으로 내부 슬롯과 내부 메서드에 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지 않음
- 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공
- [[Prototype]] 내부 슬롯에도 직접 접근할 수 없으며 __proto_ 접근자 프로퍼티를 통해 간접적으로 [[Prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근
( 2 ) __proto__ 접근자 프로퍼티는 상속을 통해 사용됨
- __proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티임
- 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티를 사용 가능
( 3 ) __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
- 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함
- 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 함
const parent = {};
const child = {};
// child의 프로토타입을 parent로 설정
child.__proto__ = parent;
// parent의 프로토타입을 child로 설정
parent.__proto__ = child; // TypeError: Cyclic __proto__ value
( 4 ) __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않음
- 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문
- __proto__ 접근자 프로퍼티 대신 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드를 사용하고,
프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용을 권장
const obj = {};
const parent = { x: 1 };
// obj 객체의 프로토타입을 취득
Object.getPrototypeOf(obj); // obj.__proto__;
// obj 객체의 프로토타입을 교체
Object.setPrototypeOf(obj, parent); // obj.__proto__ = parent;
console.log(obj.x); // 1
19.3.2 함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴
// 함수 객체는 prototype 프로퍼티를 소유
(function () {}).hasOwnProperty('prototype'); // — true
// 일반 객체는 prototype 프로퍼티를 소유하지 않음
({}).hasOwnProperty('prototype'); // — false
- 모든 객체가 가지고 있는(엄밀히 말하면 Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킴
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// 결국 Person.prototype과 me.__proto__는 결국 동일한 프루투타입을 가리킴
console.log(Person.prototype === me.__proto__); // true
19.3.3 프로토타입의 constructor 프로퍼티와 생성자 함수
-모든 프로토타입은 constructor 프로퍼티를 가지며, 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킴
- 이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이뤄짐
//생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// me 객체의 생성자 함수는 Person
console.log(me.constructor === Person); // true
➔ me 객체에는 constructor 프로퍼티가 없지만 me 객체의 프로토타입인 Person.prototype에는 constructor 프로퍼티가 있음
따라서 me 객체는 프로토타입인 Person.prototype의 constructor 프로퍼티를 상속받아 사용할 수 있음
19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재하지만, 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없음
// obj 객체는 Object 생성자 함수로 생성한 객체가 아니라 객체 리터럴로 생성
const obj = {};
// 하지만 obj 객체의 생성자 함수는 Object 생성자 함수
console.log(obj.constructor === Object); // true
- Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈객체를 생성하는 점에서 동일하나 new.target의 확인이나 프로퍼티를 추가하는 처리 등 세부 내용은 다름
➔ 따라서 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님
- 리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요
- 따라서 리터럴 표기법에 의해 생성된 객체도 가상적인 생성자 함수를 가짐
- 프로토타입은 생성자 함수와 더불어 생성되며 prototype, constructor 프로퍼티에 의해 연결되어 있기 때문
- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재
'🦎모던 자바스크립트 Deep Dive' 카테고리의 다른 글
[Chap 21]빌트인 객체 (0) | 2024.06.03 |
---|---|
[Chap 20]strict mode (0) | 2024.05.29 |
[Chap 17] 생성자 함수에 의한 객체 생성 (0) | 2024.05.15 |
[Chap 15] 스코프 (0) | 2024.05.07 |
[Chap 13] 스코프 (0) | 2024.05.01 |