모던자바스크립트 DeepDive : 19장 프로토타입

te-ing·2022년 4월 22일
0
post-thumbnail

자바스크립트는 클래스라는 개념이 없으며, 기존의 객체를 복사하여 새로운 객체를 생성하는 프로토타입 기반의 언어이다. 프로토타입 기반 언어는 객체 원형인 프로토타입을 이용하여 새로운 객체를 만들어낸다.

이같은 프로토타입은 객체지향 프로그래밍의 핵심인 객체 간 상속을 구현하기 위해 사용되며, 어떤 객체의 상위 객체 역할을 하는 객체로서 다른 객체에 공유 프로퍼티를 제공한다.

객체지향 프로그래밍

객체지향 프로그래밍은 실제 세계에서 사물이나 개념을 인식하는 철학적 사고를 프로그래밍에 접목하려는 시도에서 시작된 프로그래밍 패러다임이다.


자바스크립트의 프로토타입 기반 상속

생성자 함수를 이용하여 getArea를 상속받지 못하고 여러 번 생성해야 하는 함수

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);
console.log(circle1.getArea === circle2.getArea); // false

프로토타입 기반 상속

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);
console.log(circle1.getArea === circle2.getArea); // true

프로토타입 객체

프로토타입은 객체지향 프로그래밍의 핵심인 객체간 상속을 구현하기 위해 사용되며, 어떤 객체의 상위 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티를 제공한다.

모든 객체는 하나의 프로토타입을 가지며, 모든 프로토타입은 생성자 함수와 연결되어 있다.


proto 접근자 프로퍼티

모든 객체는 proto접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부슬롯에 간접적으로 접근할 수 있다.

proto를 이용해 프로토타입에 접근하면 getter합수인 [[Get]]이 호출되고, proto를 이용해 새로운 프로토타입을 할당하면 proto 접근자 프로퍼티의 setter함수인 [[Set]]가 호출된다.


proto 접근자 프로퍼티로 프로토타입에 접근하는 이유

프로토타입에 접근하기 위해 접근자 프로퍼티를 사용해야 하는 이유는 프로토타입 체인이 순환 참조되어 무한루프에 빠지는 것을 제한하기 위해서이다. proto 접근자 프로퍼티를 통해 접근하게 되면, 프로퍼티 검색방향이 단방향인지 체크한 뒤 교체하게 된다.


Object.getPrototypeOf, Object.setPrototypeOf 메서드

하지만 모든 객체가 proto을 사용할 수 있는 것은 아니기 때문에 코드 내에서 proto을 직접 사용하는 것은 권장되지 않는다. 직접 상속을 통해 Object.prototype을 상속받지 않는 객체도 있기 때문에, 프로토타입의 참조가 필요하다면 Object.getPrototypeOf 메서드를(ES5), 프로토타입 교체는 Object.setPrototypeOf 메서드를(ES6) 권장한다.


함수객체의 프로토타입 프로퍼티

함수 객체만 가지고 있는 prototype 프로퍼티는 생성자함수가 생성할 인스턴스의 프로토타입을 가리킨다. 따라서 생성자 함수로 호출할 수 없는 화살표함수나 ES6메서드 축약표현으로 정의한 메서드는 프로토타입 프로퍼티를 소유하지 않으며, 프로토타입도 생성하지 않는다.

리터럴 표기법에 의해 생성된 객체는 생성자 함수에 의해 생성된 객체가 아니므로 가상적인 생성자 함수를 갖는다. 따라서 함수 리터럴로 생성한 함수는 Function 생성자 함수와 생성과정, 스코프, 클로저 등의 차이를 갖지만 결국 함수로서 동일한 특성을 가지므로, 큰 틀에서 보면 리터럴 표기법으로 생성한 객체와 생성자 함수로 생성한 객체와는 본질적인 면에서 큰차이가 없다.



함수와 프로토타입의 생성 시점

함수의 프로토타입은 함수가 평가될 때 생성된다. 따라서 사용자 정의 생성자 함수는 자신이 평가되어 객체로 생성되는 시점에 프로토타입이 생성되며, 전역객체가 생성되는 시점에 생성되는 빌트인 생성자 함수는 객체가 생성되기 이전에 생성자 함수와 프로토타입이 객체화되어 존재한다.


오버라이딩과 프로퍼티 섀도잉

프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라 부르는데, 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 덮어쓰지 않고 인스턴스 프로퍼티로 추가한다. 이때 인스턴스가 프로토타입을 오버라이딩 했다고 하고, 상속관계에 의해 프로토타입이 가려지는 현상을 프로퍼티 섀도잉 이라 한다.


instanceof 연산자

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


직접상속

let obj = Object.create(Object.prototype, {
	xL {value: 1, writable: true, enumerable: true, configurable: true});
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

Object.create 메서드는 프로토타입을 지정하여 새로운 객체를 생성한다. 첫번째 매개변수에는 프토토타입으로 지정할 객체를, 두번째 매개변수는 프로퍼티키와 프로퍼티 디스크럽터 객체로 이루진 객체를 전달하며, 옵션으로 생략가능하다.

Object.create 메서드를 이용하면 다음과 같은 장점이 있다.

  • new 연산자 없이도 객체 생성 가능
  • 프로토타입을 지정하면서 객체 생성 가능
  • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있음

정적 프로퍼티/메서드

정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조,호출할 수 있는 프로퍼티/메서드를 말한다. 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.


프로퍼티 존재 확인

const person = {
	name: 'Lee',
	address: 'Seoul'
};
cosnole.log('name' in person); // true
console.log('toString' in person); // true 프로토타입 체인상에 존재하는 toString
console.log(Reflect.has(person, 'toString')); // true
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false
  • in 연산자
    in 연산자는 객체 내 특정 프로퍼티의 존재여부를 확인하며, 프로토타입 체인상에 존재하는 모든 프로토타입을 검색한다.
  • Reflect.has
    ES6에서는 in연산자 대신 Reflect.has 메서드를 사용할 수 있는데, in 연산자와 동일하게 작동한다.
  • Object.prototype.hasOwnProperty
    인수로 전달받은 프로퍼티가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고, 상속받은 프로퍼티의 경우 false를 반환한다.

프로퍼티 열거

const person = {
	name: 'Lee',
	address: 'Seoul'
};
for (const key in person) {
	console.log(key + ': ' + person[key]);
} // name: Lee address: Seoul

for ... in은 객체의 프로퍼티 개수만큼 순회하며 선언한 변수에 프로퍼티 키를할당한다. 위에서 살펴본 toString은 열거되지 않는데 이는 Object.prototype.string 프로퍼티의 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false으로, toString는 열거할 수 없도록 정의된 프로퍼티 이기 때문이다.

즉 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거하는 것이다.
상속받은 프로퍼티를 제외하려면 if(!person.hasOwnProperty(key)) 를 이용해 걸러줘야 한다.


Object.key/vaues/entrues 메서드

for...in문은 상속받은 프로퍼티까지 열거하기 때문에 고유의 프로퍼티만 열거하기 위해서는 Object.keys/values/entries 메서드를 사용하는 것을 권장한다. 이는 각각 자신의 프로퍼티 키, 값, 키와 값을 배열에 담아 반환한다.

profile
병아리 프론트엔드 개발자🐣

0개의 댓글