객체 지향 프로그래밍(OOP, Object-oriented programming) Ch 02. 프로토타입

HanSungUk·2022년 5월 29일
0

Javascript

목록 보기
14/16
post-thumbnail

객체 지향 프로그래밍(OOP, Object-oriented programming) Ch 02. 프로토타입

현재 코드스테이츠 강의 및 모던 자바스크립트 Deep Dive를 통해 프론트엔드를 학습하고 있습니다.
본 포스트는 해당 강의에 대한 내용 정리를 목적으로 합니다.

학습목표

  • 프로토타입이 무엇인지 설명할 수 있다.
  • 프로토타입과 클래스의 관계에 대해 설명할 수 있다.
  • 프로토타입 체인에 대해 설명할 수 있다.

1. 상속과 프로토타입

객체지향 프로그래밍 은 객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어 생각합니다.
따라서, 객체는 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있습니다.
이때 객체의 상태 데이터를 프로퍼티(property), 동작을 메서드(method)라고 부릅니다.

function Circle(radius){
	this.radius = radius;
  	this.getArea = function(){
		return Math.PI * this.radius ** 2;
	};
}

// 인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);

// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 메모리 낭비와 퍼포먼스의 악영향을 막는다.
console.log(circle1.getArea === circle2.getArea); // false

위 예제와 같은 불필요한 중복을 제거하기 위해 상속을 사용합니다.
자바스크립트는 프로토타입 기반 객체지향 언어 로서 프로토타입 객체(또는 줄여서 프로토타입)은 객체지향 프로그래밍의 근간을 이루는 객체 간 상속을 구현하기 위해 사용됩니다.

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 프로퍼티는 constructor(생성자 함수)만이 소유하는 프로퍼티입니다.

Circle 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입, 즉 상위(부모) 객체 역할을 하는 Circle prototype의 모든 프로퍼티와 메서드를 상속받습니다.

2. 프로토타입 객체(프로토타입)

프로토타입은 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)를 제공합니다.
프로토타입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있습니다.


모든 객체는 하나의 프로토타입 객체(프로토타입)을 갖습니다. 그리고 모든 프로토타입은 생성자 함수와 연결되어 있습니다. 프로토타입 객체(프로토타입)는 자신의 constructor 프로퍼티를 통해 생성자 함수에 접근할 수 있고, 생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입 객체(프로토타입)에 접근할 수 있습니다.

모든 객체는 [[prototype]]이라는 내부 슬롯을 가지며,
[[Prototype]] 내부 슬롯이 프로토타입을 가리킵니다(참조합니다).
객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정됩니다.

객체 생성 방식에 따라 객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototype이고 생성자 함수에 의해 생성된 객체의 프로토타입은 생성된 함수의 prototype 프로퍼티에 바인딩돼있는 객체입니다.

또한 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있습니다.
내부 작동 원리는 Object.prototype의 접근자 프로퍼티인 __proto__가 getter/setter 함수라고 부르는 접근자 함수를 통해 [[Prototype]] 내부 슬롯의 값, 즉 프로토타입을 취득(getter 함수 호출)하거나 할당(setter 함수 호출)합니다.

참고로 __proto__접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티 입니다. 따라서 모든 객체는 상속을 통해 Oject.prototype.__proto__접근자 프로퍼티를 사용하고 있습니다.

const person = {name: 'Han};
// person 객체는 __proto__ 프로퍼티를 소유하지 않습니다.
console.log(person.hasOwnProperty('__proto__')); // false

모든 객체는 프로토타입의 계층 구조인 프로토타입 체인에 묶여있습니다. 자바스크립트 엔진은 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 _proto__접근자 프로퍼티가 가리키는 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색합니다. 프로토 타입 체인의 종점. 즉 프로토타입 체인의 최상위 객체는 Object.prototype이며, 이 객체의 프로퍼티와 메서드는 모든 객체에 상속됩니다.

  • __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
    [[prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근하기 위해 접근자 프로퍼티를 사용하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서 입니다.
    프로토타입 체인은 단방향으로 연결돼야 프로토타입 체인 종점이 존재하기 때문에 프로퍼티를 검색할 때 무한 루프에 빠지지 않습니다.

  • __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

함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킵니다.

(function () {}).hasOwnProperty('prototype'); // true

({}).hasOwnProperty('prototype'); // false

즉, 모든 객체가 가지고 있는 (Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킵니다.


모든 프로토타입은 constructor 프로퍼티를 갖습니다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킵니다.

또한 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성되며,
prototype, constructor 프로퍼터에 의해 연결되어 있습니다.

function Person(name){
	this.name = name;
}

const me = new Person('Han');

// me 객체의 생성자 함수는 Person이다.
console.log(me.constructor === Person); // true

위 예제에서 me 객체에는 constructor 프로퍼티가 없지만 me 객체의 프로토타입 객체인 Person.prototype에서 constructor 프로퍼티를 상속받아서 생성자 함수와 연결할 수 있습니다.


  • 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

리터럴 표기법(객체 리터럴, 함수 리터럴, 배열 리터럴, 정규 표현식 리터럴 등)에 의해 생성된 객체는 생성자 함수에 의해 생성된 객체와 생성 과정에서 미묘한 차이는 있지만 결국 객체로서 동일한 특성을 갖습니다.
따라서 프로토타입의 constructor 프로퍼티를 통해 연결되어 있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성한 생성자 함수로 생각해도 크게 무리가 없습니다.

// 객체 리터럴
const obj = {};

// 함수 리터럴
const add = function(a, b){return a + b};

// 배열 리터럴
const arr = [1, 2, 3];

// 정규 표현식 리터럴
const regexp = /is/ig
리터럴 표기법생성자 함수프포토타입
객체 리터럴ObjectObject.prototype
함수 리터럴FunctionFunction.prototype
배열 리터럴ArrayArray.prototype
정규 표현식 리터럴RegExpRegExp.prototype

3. 프로토타입 체인

자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색합니다. 이를 프로토타입 체인이라고 합니다. 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘 입니다.

function Person(name){
	this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHello = function() {
  console.log(`Hi! My name is ${this.name}`);
};

const me = new Person('Lee');

// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true

Person 생성자 함수에 의해 생성된 me 객체의 프로토타입은 Person.prototype 입니다. 하지만 me 객체는 Object.prototype의 메서드인 hasOwnProperty를 호출할 수 있습니다. 이는 me 객체가 Person.prototype 뿐만 아니라 Object.prototype도 상속받았다는 의미입니다.
Person.prototype의 프로토타입은 Object.prototype입니다. 프로토타입의 프로토타입은 언제나 Object.prototype입니다.

Object.prototype을 프로토타입 체인의 종점(end of prototype chain)이라 합니다. Object.prototype의 프로토타입, 즉 [[Prototype]]내부 슬롯의 값은 null입니다.

프로트타입 체인의 종점 Object.prototype에서도 프로퍼티를 검색할 수 없는 경우 에러가 발생하지 않고 undefined를 반환합니다.

정리하면 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이고 스코프 체인은 식별자 검색을 위한 메커니즘이라고 할 수 있습니다.

0개의 댓글