본문 바로가기
Front-End/JavaScript

19장 프로토타입 (모던 자바스크립트 Deep Dive)

by kk님 2023. 4. 10.

 

모던 자바스크립트 Deep Dive 글 목록(스터디)
https://hello-kk.tistory.com/780
 
프로토타입 단원을 읽으면서...
"내가 그린 구름그림은 새털구름 그린 구름그림이고, 네가 그린 구름그림은 깃털구름 그린 구름그림이다."
가 생각난건 나 뿐만이 아닐 것으로 생각된다..
 
핵심 단어들을 모아 정리했다.
https://hello-kk.tistory.com/788

 
 
자바스크립트는 클래스 기반 객체지향 프로그래밍 언어보다 효율적이며,
더 강력한 객체지향 프로그래밍 능력을 지니고 있는 프로토타입 기반의 객체지향 프로그래밍 언어다

 
책에서 나타낸 문장은 3가지를 말하는 듯 하다. 아마 결국 이해 할 내용이지 않을까?
1. 클래스 기반 ↔ 프로토타입 기반
2. 자바스크립트는 객체 지향 프로그래밍 언어
3. 프로토 타입 기반이 더 강력하다
 
프로퍼티: 객체의 상태
메서드: 객체의 동작
 
상속 이유: 불필요한 중복을 제거=> 코드의 재사용
 
책에서는 Circle을 예로 들어서 설명했는데, 생성자 함수를 사용했다.
radius(프로퍼티)는 각 인스턴스마다 다른 값을 갖지만,
getArea(메서드)는 모든 인스턴스가 동일한 내용으로 구성된다(radius를 사용하지만, 원주율을 구하는 것이 이 메서드의 목적이다. 따라서, radius만 알게 되면 원주율을 구할 수 있다.)
모든 인스턴스에서 getArea메서드가 공통으로 사용되지만 매번 인스턴스가 생성되면서 중복으로 메서드가 생성되기 때문에 매 인스턴스마다 getArea 메서드가 새로 메모리를 차지하게 된다.
하지만, 프로토타입을 사용한다면 메모리를 절약할 수 있다.

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

프로토 타입 기반 상속 코드 작성 방법

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

Circle 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입인 상위(부모) 객체 역할을 하는 Circle.prototype의 모든 프로퍼티와 메서드를 상속받는다.
=> 즉, getArea 메서드는 딱 한번 생성되고, 이후는 프로토 타입의 getArea 메서드를 모든 인스턴스가 공유한다.
 

코드의 재사용
생성자 함수가 생성할 모든 인스턴스공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현,
상위 객체인 프로토 타입의 자산을 공유하여 사용한다.
 

프로토타입 객체: 객체간 상속을 구현하기 위해 사용
모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖고, 저장되는 프로토타입은 객체 생성 방식에 의해 결정

객체 생성 방식?

인스턴스의 부모=>프로토타입
객체의 [[Prototype]] 내부 슬롯은 => 일단 생성자 함수로부터 생긴 인스턴스를 의미하는 건가?
 
생성 방식은 두가지이다(19.6절에서 더 자세히 다룬다고 한다)
1. 객체 리터럴에 의해 생성된 객체=> 프로토타입은 Object.prototype
2. 생성자 함수에 의해 생성된 객체=> 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어있는 객체
 
모든 객체는 하나의 프로토타입을 갖고, 모든 프로토타입은 생성자 함수와 연결되어 있다.
 
생성자 함수[[Prototype]] 내부 슬롯   ←  ( __proto__ ) ← 객체
생성자 함수  →  생성자 함수.prototype  ←  ( __proto__ ) ← 객체
생성자 함수  ← ( constructor ) ←  생성자 함수.prototype 
 
객체의..? [[Prototype]] 내부 슬롯에는 직접 접근할 수 없다는 의미인가 ? 265 페이지
 
생성자함수의 prototype 프로퍼티에 바인딩되어있는 객체=> 프로토타입
 
1. 생성자 함수는 생성자함수.prototype으로 프로토타입에 접근
2 객체의 경우 __proto__ 접근자를 통해 자신의 프로토타입, [[Protorype]] 내부 슬롯에 간접적으로 접근
내부슬롯과 객체는 참조 관계?
3. 프로토타입은 constructor 프로퍼티를 통해 생성자 함수에 접근가능
=> 생성자 함수, 생성자 함수의 프로토타입, 객체 간의 관계 이해하기
 
__proto__ 접근자 프로퍼티

객체의 경우 __proto__ 접근자를 통해 자신의 프로토타입, [[Protorype]] 내부 슬롯에 간접적으로 접근
 

그러면, 프로토타입은 언제 생성되고 언제 연결되는지 궁금하다.

 
내부 슬롯에 직접적으로 접근할 수 없기 때문에, __proto__ 접근자 프로퍼티를 사용해야 한다.
__proto__는 값을 가져올때는 getter 함수가 호출되고, 새로운 프로토타입을 할당하면 setter함수가 호출된다.
 
결국 객체의 부모를 정하는 역할이 __proto__
__proto__ 접근자는 객체가 직접 소유하지 않는다.=> Object,prototype의 프로퍼티

const person = { name: 'hello' }
console.log(person.hasOwnProperty('__proto__') //(1)

console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'))

console.log({}.__proto__ === Object.prototype)

여기서 마지막 부분에, {}.__proto__ === Object.prototype.__proto__ 가 true 일 것이라고 생각했는데, 이 생각은 잘못됐다.
Object.prototype.__proto__는 null을 가리킨다.
Object.prototype이 프로토타입 체인의 최상위 객체이기 때문에!
따라서 {}.__proto__.__proto__를 따라가면 null이 나온다.
{}.__proto__는 Object.prototype를 의미한다.

 
그런데 .. 내가 헷갈리는 부분이 Object.prototype 이 프로토타입인가? (뭔가 당연한 말인데..) 왜 헷갈리지?

__proto__ 프로퍼티와 prototype가 왜 같지?

 
__proto__는 접근자였는데, [[Prototype]] 내부슬롯의 값인 프로토타입에 접근할 수 있게 해준다.
즉, {}.__proto__는 [[Prototype]]의 내부 슬롯의 값프로토타입에 접근할 수 있게 한다.
=> {}.__proto__는 프로토타입 객체를 가리킨다. 프로토타입의 객체란, Object.prototype 자체가 객체다!
 

TODO: ★__proto__ 접근자 프로퍼티와 프로토 타입의 프로퍼티는 어떤 차이가 있는지 알아보기★

 
프로토타입의 최상위 객체는 Object.prototype, 이 객체의 프로퍼티와 메서드는 모든 객체에 상속된다.
 
책에서 말하고자 하는 바를 정리하면 아마도, __proto__는
1. 인스턴스 객체가 소유하는 프로퍼티가 아니다.
2. __proto__는 Object.prototype의 프로퍼티이다.
3. 그런데, 모든 객체는 __proto__를 사용할 수 있다. 왜냐면, Object.prototype의 __proto__를 상속받았기 때문에.
 

여기서, 특정객체.__proto__란, 특정객체의 프로토타입(객체)를 (의미한다)가리킨다.

 
__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서이다.. (아직 잘 모르겠다)
설명을 읽어보면 parent와 child가 서로의 프로토타입으로 설정되는건 확실히 이상하다. 그런데 __proto__ 접근자 프로퍼티가 어떤 역할을 하길래 막을 수 있는걸까?
=> __proto__ 접근자가 프로토타입을 교체할 때 체크를 하는 것 같다.
 
__proto__ 접근자 프로퍼티를 직접 사용하는 대신,
1. 프로토타입의 참조를 취득하고 싶은 경우, Object.getPrototypeOf 메서드를 사용
2. 프로토타입을 교체하고 싶은 경우는, Object.setPrototypeOf 메서드를 사용
 
★프로토타입( 객체)란,
상속받은 객체들이 공통적으로 참조할 수 있는 프로퍼티나 메서드를 정의하는 역할을 한다는 것을 기억해야 할 것 같다.
(또, 프로토타입의 프로퍼티는 프로퍼티와 메서드를 포함한다는 것을 헷갈리면 안되는 것 같다.. 말이 똑같아서 헷갈리는 것 같다.)
 
프로토타입에 대한 사전적 정의는 원래의 형태라고 한다. (시제품 정도로만 알고있었는데)
 
prototype 프로퍼티랑 prototype 은 어떤 차이지? 너무 헷갈린다
prototype( 객체): 객체의 부모 역할을 하는 객체.
prototype 프로퍼티: 해당 객체가 상속받을 부모 객체를 지정하는 용도로 사용되는 프로퍼티. 해당 생성자 함수로 생성된 객체들이 상속받을 부모 객체를 지정하는 역할을 한다. => 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
 
함수 객체의 prototype 프로퍼티. 라고 기억하는 편이 더 헷갈리지 않는 것 같다.
 
__proto__ 접근자 프로퍼티와 prototype 프로퍼티는 동일한 프로토타입(객체)를 가리키지만, 주체가 다르다.
__proto__ 접근자 프로퍼티: 모든 객체
prototype 프로퍼티: 생성자 함수
 
프로토타입 객체는 constructor 프로퍼티를 갖는다. => 프로토타입 객체의 프로퍼티엔 constructor가 있다.
이는 자신을 참조하는 생성자 함수를 가리킨다.
연결은 (가리키는 것) 생성자 함수가 생성될 때 (함수 객체가 생성될 때) 이뤄진다.
 
상속받은 객체는 생성자를 곧바로 참조하지 않고, 프로토타입 객체에 있는 프로퍼티를 상속받아 사용.
 
생성자 함수와 프로토타입 객체는 쌍으로만 존재한다.
프로토타입 객체는 생성자 함수가 생성되는 시점에 같이 생성된다.
1. 사용자 정의 생성자 함수와 프로토타입 생성 시점
 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됨 (프로토타입 객체? 프로토타입 프로퍼티?)
그러나, 표현식으로 표현되는 화살표 함수는 함수 정의가 평가되는 시점에서 식별자만 존재하기 때문인지 프로토타입 프로퍼티가 생성되지 않는건가? 해당 함수의 프로토타입 객체는 생성되고..?
 
함수 객체의 프로토타입 프로퍼티에는 constructor 가
 
함수 객체의 프로토타입 프로퍼티에는 어떤 값이 들어가지?
 
프로토타입 객체랑 함수 객체의 프로토타입 프로퍼티는 무슨 관계지?
 
2. 빌트인 생성자 함수와 프로토타입 생성 시점
전역 객체가 생성되는 시점에 생성된다.
 
Object.protype을 프로토타입으로 갖게 된다=> 프로토타입을 상위 객체라고 생각해야 할까?
 
프로토타입 객체인 Person.prototype에 프로퍼티를 추가하게 되면 Person 생성자 함수를 통해 프로퍼티를 상속받는 인스턴스는 프로퍼티를 사용할 수 있다.(여기서 프로퍼티에는 프로퍼티,메서드 가 들어갈 수 있다)
 
프로퍼티와 메서드는 프로토타입 체인을 따라 검색
프로퍼티가 아닌 식별자는 스코프 체인에서 검색
 
1. me 식별자를 스코프 체인에서 검색
2. me 객체의 프로토타입 체인에서 메서드를 검색