[JS] 프로토타입기반 객체지향

자바스크립트는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어이다.

자바스크립트는 객체기반의 프로그래밍 언어이며 자바스크립트를 이루고 있는 거의 '모든 것' 이 객체이다. (원시타입의 값을 제외한 나머지 값은 모두 객체이다.)

객체지향 프로그램은 실세계의 실체를 인식하는 철학 사고를 바탕으로 프로그래밍을 하는 것인데,
여기서 실체를 객체 (Object)의 형태로 구현하며 실체가 가지는 여러가지 특징이나 성질을 객체 내 속성(Attribute/Property)로 구현한다.

추상화란 사람이라는 객체 내부에 '이름','주소'라는 속성을 개발자가 지정하여 필요한 속성만 간추려 내어 표현하는 것을 말한다.

const person = {
	name: "Lee";
    	address: "Seoul";
};

객체 내에는 객체의 상태를 나타내는 데이터와 데이터를 조작할 수 있는 동작이 하나의 논리적 단위(객체)로 묶여있다. 상태 데이터를 프로퍼티(Property), 동작을 메서드(Method)라 부른다.

객체는 고유의 가능을 갖는 독립적인 부품으로 볼 수 있지만 다른 객체와 관계성 또한 가질 수 있다. 데이터를 처리하거나, 상태 데이터나 동작을 상속받아 사용하기도 한다.


상속과 프로토타입

상속(inheritance) -> 기존에 정의된 객체의 프로퍼티, 메소드를 다른 객체가 그대로 사용할 수 있다.
이는 불필요한 코드의 중복을 제거한다.

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 메서드를 중복생성하고 모든 인스턴스가 중복 소유한다. 
console.log(circle1.getArea === circle2.getArea); //false

console.log(circle1.getArea());  //3.141592...
console.log(circle2.getArea());  //12.566370...

위 코드를 통해 Circle 객체를 여러번 생성하면 생성 할 때마다 같은 동작을 하는 getArea는 circle의 각 객체마다 저장되어 메모리를 과도하게 잡아먹게 된다. 이러한 불필요한 낭비를 막기위해

객체보다 상위 객체를 만들어 (상속을 통해) 메서드를 불러오도록 동작하면 getArea에 대한 각 인스턴스 속 불필요한 저장공간을 줄일 수 있지 않을까?

라는 개념을 도입한 것이 프로토타입의 주요 존재 이유이다.

function Circle(radius) {
	this.radius = radius;
};

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

const circle1 = new Circle(1);
const circle2 = new Circle(2);

//Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는
//프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다. 
console.log(circle1.getArea === circle2.getArea); //true

console.log(circle1.getArea());  //3.141592...
console.log(circle2.getArea());  //12.566370...

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

이를 통해 각각 인스턴스에게 필요한 고유한 데이터 프로퍼티는 개별적으로 보유하되, 같은 동작을 하는 메서드는 상속을 통해 프로토타입에서 꺼내 사용할 수 있다.

이는 메모리 관점에서도 매우 유용하며, 여러개의 인스턴스를 효율적으로 만들어 낼 수 있다.


프로토타입 객체

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지고 있으며, 이 내부 슬롯의 값은 프로토타입의 참조이다.

모든 객체는 하나의 프로토타입을 가지며, 객체 생성 방식에 따라 [[Prototype]]에 저장되는 프로토타입이 결정되게 된다. 모든 프로토타입은 생성자 함수와 연결되어 있다.

__proto__ 접근자 프로퍼티

[[Prototype]] 내부 슬롯에는 직접적으로 접근할 수 없지만 __proto__ 접근자 프로퍼티를 통해 간접적으로 접근할 수 있다.

__proto__는 [[Prototype]]이라는 내부슬롯의 값에 접근할 수 있게 해주는 접근자 프로퍼티로 여느 다른 접근자 프로퍼티와 같이 [[Get]], [[Set]] 프로퍼티 어트리뷰트를 갖고 있다.

__proto__를 값을 불러오는데 사용되면 getter 함수가 호출되어 객체의 프로토타입을 취득하며, 값을 할당하는데 사용되면 setter 함수가 호출되어 객체의 프로토타입을 교체한다.

const obj = {};
const parent = {x:1};

//get함수 호출 obj 객체의 프로토타입을 반환
obj.__proto__; 

//set함수 호출 obj 객체의 프로토타입을 parent로 교체
obj.__proto__ = parent'

console.log(obj.x); //1

__proto__는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티이다. 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티를 사용할 수 있다.

Object.prototype은 모든 객체가 묶여있는 계층구조, 프로토타입 체인의 최상위 종점에 위치하는 객체이며, 이 객체의 프로퍼티와 메서드는 모든 객체의 생성 즉시 상속된다.
(마치 모든 객체의 시조이자 근본 같은 뿌리)

__proto__는 상호 참조로 인해서 프로토타입 체인이 루프에 빠져버리는 것을 방지하기 위해 사용된다. 상위 부모객체의 프로토타입이 자식객체로 지정되는 경우 프로토타입 체인이 꼬이게 되고 단방향 링크드 리스트로 구현되지 않는다. 순환참조로 이루어지면 프로토타입체인에서 프로퍼티를 검색할 때 무한루프에 빠지기 떄문에 __proto__접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현되어 있다.

그런데 모든 객체가 Object.prototype을 상속받지 않기 때문에 __proto__접근자 프로퍼티의 코드 내 사용을 권장하지는 않는다. __proto__ 보다 프로토타입의 참조를 취득하고 싶은 경우 Object.getPrototypeOf 메서드 사용을 권장하고, 프로토타입을 교체하고 싶은 경우 Object.setPrototypeOf 메서드를 사용할 것을 권장한다. (IE11이상에서 지원한다.)

좋은 웹페이지 즐겨찾기