JavaScript 프로토타입_정의 및 constructor, 프로토타입 체인, 상속 등

binu·2023년 1월 21일
0
post-thumbnail

JavaScript - 프로토타입

프로토타입이란?

자바스크립트는 프로토타입 기반의 언어이다. 프로토타입이란 객체지향 프로그래밍의 근간을 이루는 객체 간 “상속(inheritance)”을 구현하기 위해 사용된다. 이는 어떤 객체의 부모 객체 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)를 제공한다. 프로토타입을 상속받은 자식 객체는 부모 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있다.

상속(inheritance)은 왜?
상속은 객체지향 프로그래밍의 핵심인데, 어떤 객체의 프로퍼티/메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다.

자바스크립트는 ‘프로토타입’을 기반으로 상속을 구현해 불필요한 중복을 제거한다. 중복을 제거하는 방법은 기존의 코드를 적극적으로 재사용하는 것인데, 상속이 중복 코드 제거에 효과적으로 도움을 준다.


constructor

모든 프로토타입은 constructor 프로퍼티를 갖는다. 이는 prototype 프로퍼티로, 자신을 참조하고 있는 생성자 함수를 가리킨다. 이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이루어진다.

// 아래 예제는 '모던 자바스크립트' p.271 예제 참고

// 생성자 함수
function Person(name) {
	this.name = name;
}

const me = new Person('Lee');
// me 객체의 생성자 함수는 Person이다.
// => 어떤 생성자로부터 생성된 것인지 유추 가능케함
console.log(me.constructor === Person);    // true

(+) instanceof 연산자

이 연산자는 이항 연산자로, 좌변에는 객체를 가리키는 식별자, 우변에는 생성자 함수를 가리키는 식별자를 피연산자로 받는다.

객체 instanceof 생성자 함수

우변의 생성자 함수의 프로토타입에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 ture, 그렇지 않으면 false이다.

// 아래 예제는 '모던 자바스크립트' p.296 예제 참고

// 생성자 함수
function Person(name) {
	this.name = name;
}

const me = new Person('Lee');

// Person.prototype이 me 객체의 프로토타입 체인 상에 존재 -> true
console.log(me instanceof Person);   // true

// Object.prototype이 me 객체의 프로토타입 체인 상에 존재 -> true
console.log(me instanceof Object);   // true

// Person.prototype이 parent 객체의 프로토타입 체인 상에 존재 -> false
console.log(parent instanceof Person);   // false (parent라는 객체 존재X)

instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아닌, 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지를 확인한다.


_ _proto_ _

_ _proto_ _ 는 접근자 프로퍼티

접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다. (+ 자바스크립트는 원칙적으로 내부 슬롯/내부 메서드에 직접적으로 접근하거나 호출하는 방법을 제공하지 않음)

접근자 프로퍼티는 자체적으로는 값을 갖지 않으며, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수인 ‘getter’, ‘setter’함수로 구성된다.
→ 이 경우에는 [[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티이다.

// 아래 예시 코드는 '모던 자바스크립트' p.266 예제 코드 참고

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

// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;

// setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

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

_ _proto_ _는 상속을 통해 사용

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

_ _proto_ _ 를 통해 프로토타입에 접근하는 이유

프로토타입에 접근하기 위해 접근자 프로퍼티(_ _proto_ _)를 사용하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함이다.

// 아래 예제 코드는 '모던 자바스크립트' p.267 예제 코드 참고

const parent = {};
const child = {};

// chlid 의 프로토타입을 parent로 설정
child.__proto__ = parent;

// parent의 프로토타입을 child로 설정
parent.__proto__ = child;  // TypeError: Cyclic __proto__ value

위 예제 내용은 parent 객체를 child 객체의 프로토타입으로 설정한 후, child 객체를 parent 객체의 프로토타입으로 설정한 것이다. 이는 parent 객체가 child의 ‘부모’인 동시에 ‘자식’이 되고, child 객체가 parent 객체의 ‘자식’인 동시에 ‘부모’가 되는 순환 참조 구조가 되어 무한 루프에 빠지게 된다.

프로토타입 체인은 단방향 링크드 리스트로 구현되어야 하는데, 이런 순환 구조가 만들어지지 않도록 _ _proto_ _ 접근자 프로퍼티를 통해 프로토타입에 접근하고, 교체하도록 구현되어 있다. (위 예제 내용대로 작성하더라도 _ _proto_ _ 로 구현 시, 실행하면 ‘TypeError’ 를 발생시키기 때문)

👎 MDN 및 공부한 책 내용에 따르면 _ _proto_ _의 사용은 논란의 여지가 있어 더이상 사용되지 않길 바라는 기술이라고 한다.


프로토타입 체인

자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면, 프로토타입 내부 슬롯의 참조를 따라 부모 역할을 하는 프로토타입 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 하며, 프로토타입 체인은 자바스크립트가 OOP의 상속을 구현하는 메커니즘이다.

프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype으로, 모든 객체는 Object.prototype을 상속받는다. 따라서 Object.prototype을 프로토타입 체인의 종점(end of prototype chain)이라고 한다. 참고로 Object.prototype의 프로토타입 값은 null 이다.

프로토타입 체인의 종점인 Object.prototype에서도 프로퍼티를 검색할 수 없는 경우,
undefined를 반환하므로, 에러가 발생하지 않는다는 점을 주의한다.

프로토타입 확장(~상속)

Object.create에 의한 직접 상속

자식.prototype = Object.create(부모.prototype)

-> 예제

// MDN - Object.create() 예제 참고

// Shape - 상위 클래스
function Shape() {
	this.x = 0;
	this.y = 0;
}

// 상위 클래스 메서드
Shape.prototype.move = function(x,y) {
	this.x += x;
	this.y += y;
	console.info('Shape moved.');
}

// Rectangle - 하위 클래스
 function Rectangle() {
	Shape.call(this);   // super 생성자 호출
}

// 하위 클래스는 상위 클래스를 확장(상속)
Rectangle.prototype = Object.create(Shpe.prototype);
Rectangle.prototype.constructor = Rectangle;

(+) 객체 리터럴 내부에서 _ _proto_ _ 에 의한 직접 상속

// 예시 (해당 예시 코드는 '모던 자바스크립트' p.302 예제임)
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

> 👎 프로토타입 상속은 비추천 방식
        👍 오히려 클래스 상속 방식을 추천

상속(~확장) 구조, 이렇게 이해해보면 어떨까?

const arr = [];

arr instanceof Array;
arr instanceof Object;




마무리
HTML과 CSS를 배우면서 너무나도 흥미로웠다. 코드 몇 줄 적은 걸로 디자인적인 요소가 나오다니! 그렇지만 웹 서비스는 보기 좋게 만드는 것 이전에 웹에서 사용자가 데이터를 받고, 전송할 수 있는 '사용할 수 있는' 서비스여야 정말 서비스인 것 같다. 현재 우리가 실사용하는 대부분의(아마... 모든) 웹 서비스는 자바스크립트 기반이지 않을까 싶으니 말이다. 그래서 자바스크립트를 꼭 배워야하고, 통신 부분도 정말 중요한 것 같다는 생각이 들었다. (지금은 무엇이든 열심히 배워야하지만!) 그래서 이번 주 스터디 시간에는 다른 파트와 더불어 프로토타입 요약을 맡아 좀 더 열심히 공부했다. 갈수록 이해도, 정리하는 데도 적지 않은 시간이 들지만, 지금 당장은 조금 부족해도 꾸준히 하자!



profile
예비 프론트엔드 개발자, 아기 binu

0개의 댓글