[JS딥다이브] 9. 프로토타입

게코젤리·2023년 5월 15일

1. 객체지향 프로그래밍

프로그램에 필요한 핵심 속성을 추상화한 뒤, 객체의 상태(프로퍼티)와 동작(메서드)을 하나의 논리적인 단위로 묶어 효율적이고 이해하기 쉬운 프로그램을 만들어 내는 방식.

2. 상속과 프로토타입

자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 없앤다.

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

위와 같이 생성자 함수를 통해 여러개의 인스턴스를 만들어내는 경우 매번 동일한 getArea 메서드를 생성한다. 이와 같은 중복을 제거하기 위해 프로토타입을 기반으로 상속을 구현한다. 즉, 시조에 정보를 새기고 자손들은 필요할 때마다 시조에게 물어봐서 정보를 가져온다.

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

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

3. 프로토타입 객체

프로토타입 객체란 위에서 설명한 시조에 정보를 새긴 것을 모아둔 객체다. 모든 객체는 하나의 프로토타입을 갖고 생성자 함수와 연결되어 있다.

3-1. __proto__ 접근자 프로퍼티

모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, [[prototype]] 내부 슬롯에 간접적으로 접근할 수 있다. __proto__ 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티다. 모든 객체는 상속을 통해 Object.prototype.__proto__ 프로퍼티를 사용할 수 있다.

1) __proto__접근자 프로퍼티를 통해 프로토타입에 접근하는 이유.

순환 참조하는 프로토타입 체인이 만들어지면 프로토타입의 체인 종점이 존재하지 않기 때문에 프로퍼티를 검색할 때 무한루프에 빠진다. 때문에 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 한다.

2) __proto__접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.

모든 객체가 proto 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문.
만약 프로토타입의 참조를 획득하고 싶다면 Object.getPrototypeOf 메서드를 사용하고, 프로토타입을 교체하고 싶다면 Object.setPrototypeOf 메서드를 사용하자.

3-2. 함수 객체의 prototype 프로퍼티

prototype 프로퍼티는 함수 객체만 소유하며 이는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다. 일반 객체 또는 non-constructor(화살표 함수, 메서드)는 prototype 프로퍼티를 소유하지 않는다.

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

3-3. 프로토타입의 constructor 프로퍼티

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

4. 프로토 타입의 생성 시점

프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.

4-1. 사용자 정의 생성자 함수와 프로토타입 생성 시점

  • 생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
  • 생성자 함수로서 호출할 수 없는 함수, 즉 non-constructor는 프로토타입이 생성되지 않는다.
  • 프로토타입도 객체이고 모든 객체는 프로토타입을 가지므로, 프로토타입도 자신의 프로토타입을 가지는데 그 객체는 Object.prototype이다.

4-2. 빌트인 생성자 함수와 프로토타입 생성 시점

객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재한다.
이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다.

5. 객체 생성 방식과 프로토타입의 결정

객체는 다양한 방식으로 생성되지만 추상 연산 OrdinaryObjectCreate에 의해 생성된다는 공통점이 있다. OrdinaryObjectCreate는 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달 받는다. 즉, 프로토 타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정된다.

5-1. 객체 리터럴에 의해 생성된 객체의 프로토타입

  • 객체 리터럴 const obj = {}로 생성된 obj 객체는 Object.prototype을 프로토타입으로 갖게 되며, Object.prototype을 상속받는다.
  • obj 객체는 constructor 프로퍼티와 hasOwnProperty 같은 메서드 등을 소유하진 않지만, 자신의 프로토타입인 Object.prototype 객체를 상속 받았기 때문에 constructor 메서드와 hasOwnProperty 메서드를 자유롭게 사용할 수 있다.

5-2. Object 생성자 함수에 의해 생성된 객체의 프로토타입

객체 리터럴 방식은 프로퍼티를 추가할 때 객체 리터럴 내부에 프로퍼티를 추가해야 하지만 Object 생성자 함수는 일단 빈 객체를 생성한 이후 프로퍼티를 추가해야 한다.

5-3. 생성자 함수에 의해 생성된 객체의 프로토타입

  • new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate를 호출한다.
  • 이 때 OrdinaryObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.
  • 사용자 정의 생성자 함수와 생성된 프로토타입의 프로퍼티는 constructor 뿐이다.

6. 프로토타입 체인

자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없으면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 한다.

  • 프로토타입 체인의 종점인 Object.prototype 까지 프로퍼티 검색을 했는데 해당 프로퍼티가 없다면 undefined를 반환한다. 이 때 에러를 뱉지 않는다는 것에 주의하자.
  • 스코프 체인은 식별자 검색을 위한 메커니즘이고, 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이라고 보면된다.
  • 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.

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

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색하여 떺어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다. 이와 같이 상위 객체, 클래스가 가지고 있는 메서드를 하위 객체, 클래스가 재정의 사용하는 방식을 오버라이딩이라고 한다. 또한 이처럼 상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라고 한다.

내용이 많이 어려워서 다시 한 번 읽어보고 추가 정리해볼 것!

📖 모던자바스크립트 딥다이브 19장 프로토타입(259p)

0개의 댓글