1. 프로토타입의 개념 이해
클래스와 상속의 관계를 JS에선 프로토타입으로 대체하고 있다.
1-1) constructor, prototype, instance
var instance = new Constructor();
위 코드를 저 도식에 비유해보자
- 어떤 생성자 함수(Constructor)를 new 연산자와 함께 호출하면
- Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
- 이때 instance에는 __proto__라는 프로퍼티가 자동으로 부여되는데
- 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조한다.
prototype은 객체다. 당연히 __proto__도 객체다. prototype 내부에는 인스턴스가 사용할 메서드를 저장한다. 그러면 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 이 메서드들에 접근할 수 있게 된다!
예제를 통해 살펴보자, Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출할 수 있다.
- undefined 출력의 이유는 바인딩된 대상이 잘못돼서이다.
- 메서드는 바로 앞 객체가 곧 this가 된다고했는데, 위와 같은 경우에선 suzi가 아니라 suzi.__proto__라는 객체가 this가 된 것이다..! 그리고 suzi.__proto__라는 객체 내부에는 name 프로퍼티가 없으므로 undefined를 반환하는 것이다.
- 그럼 위와같이 suzi.__proto __ 객체에 name 프로퍼티를 할당해주면 출력이 잘 되는 모습이다.
- this를 인스턴스로 하려면 프로토를 빼고 메서드를 바로 써주면 된다.
- 위 코드와 같이 말이다. __proto__를 쓰지않고도 이게 되는 이유는 __proto__가 생략 가능한 프로퍼티이기 때문!!
- __proto__가 생략 가능한단건 이해의 영역이 아니라 그냥 그렇게 만들어진거다. 그냥 그렇게 알면 된다^-^
그래서 위 처럼 __proto__를 생략하면 suzi.__proto__에 있는 getName()을 실행하며 this는 suzi를 바라보게 할 수 있다.
new 연산자로 constructor를 호출하면 instance가 만들어지는데 이 instance의 생략가능한 프로퍼티인 __proto__는 Constructor의 prototype을 참조한다!
Array 함수를 통해 예제를 살펴보자
- 왼쪽은 arr의 출력결과, 오른쪽은 생성자 함수인 Array를 출력한 결과이다.
- 왼편부터 살펴보면 첫줄엔 Array(2)라고 돼 있다. Array라는 생성자 함수를 원형으로 삼아 생성 됐단 말이고 length가 2이다, 인덱스인 0, 1이 짙은 색상으로, length와 __proto__가 옅은 색상으로 표기 된다.
- __proto__ 내부에는 옅은 색상의 다양한 메서드들이 길게 펼쳐지는데 여기는 Array에서 사용하는 대부분의 메서드들이 다 들어있다.
- 오른쪽엔 함수라는 의미의 f가 표시 돼 있고, 둘째줄부턴 함수의 기본적인 프로퍼티들이 있다.
- Array함수의 prototype을 열어보니 왼편의 __proto__와 완전히 동일한 내용으로 구성되어 있다.
- Array를 new 연산자와 함께 호출해서 인스턴스를 생성하거나, 배열 리터럴을 생성하거나 해서 instance[1,2]를 만든다
- 이 인스턴스의 __proto__는 Array.protype을 참조하는데, __proto__가 생략 가능하도록 설계 돼 있기 때문에 인스턴스가 push, pop, foreach등의 메서드를 마치 자신의 것처럼 호출할 수 있다.
1-2) constructor 프로퍼티
- 생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 있다. 인스턴스의 __proto__ 객체 내부에도 마찬가지다.
- 이 프로퍼티는 단어 그대로 원래의 생성자 함수(자기 자신)을 참조 한다.
- 자신을 참조하는 프로퍼티를 굳이 갖고있는 이유는? 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단이기 때문
2. 프로토타입 체인
2-1) 메서드 오버라이드
- iu 자체적으로도 getName을 가지고 있으니 __proto__의 getName이 아니라 iu 자체의 getName이 호출되었다
- 이를 메서드 오버라이드라고 하는데, 원본을 제거하는 것이 아니라 원본 위에 다른 대상을 얹는 것이기 때문에 원본에도 접근은 가능하다.
- JS엔진이 getName을 검색할때 가장 먼저 자기 자신의 프로퍼티를 검색하고 없으면 그 이후 __proto__ 를 검색하기 때문에 자기 자신의 getName이 호출 되는 것이다.
2-2) 프로토타입 체인
- 배열의 내부 구조이다. __proto 안에 또 __proto__가 있는 모습이다. prototype객체가 '객체'이기 때문에 이 객체도 __proto__를 갖고 있는 것이 당연하고, 프로토타입 객체의 __proto__는 Object.prototype을 참조한다.
- 그림으로 보면 위와 같은 구조이며 그렇기에 당연히 Object.prototype 내부의 메서드도 사용 가능한 것이다.
- 위 처럼 __proto__ 내부에서 __proto__가 연쇄적으로 이뤄진 것을 프로토타입 체인 이라고 하며, 이 체인을 검색하며 따라가는 것을 프로토타입 체이닝 이라고 한다.
- 변수 arr 는 배열이므로 arr.__proto__는 Array.prototype을 참조할 것이고 Array.prototype 또한 객체이므로 Array.prototype.__proto__는 Object.prototype을 참조할 것이다.
- 6번줄에 arr.toString()을 함수로 정의 시켜준 이후엔 arr.toString()은 함수로써 동작할 것이다.
2-3) 객체 전용 메서드의 예외사항
- 어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재하게 된다. 따라서 객체에서만 사용할 메서드는 일반적인 경우처럼 프로토타입 객체 안에 정의 할 수가 없다.
- 다음과 같이 Object 내에서만 사용할 의도로 getEntries함수를 생성했지만 11~16번줄의 결과처럼 객체가 아니여도 getEntries()에 접근가능 해 오류없이 결과를 내고 있다.
- 어느 데이터 타입이건 프로토타입 체이닝을 통해 Object.prototype에 접근할 수 있다보니 위와 같은 현상이 발생한다
- 따라서 Object 내부에서만 사용할 메서드는 부득이하게 Object.prototype이 아닌 Object에 스태틱 메서드로 부여할 수 밖에 없다. 또한 생성자 함수인 Object와 인스턴스 객체 사이에는 this로 연결이 불가능하기 때문에 직접 인자를 주입하는 방식으로 구현해야 한다.
2-4) 다중 프로토타입 체인
- 프로토타입 체인을 2단계가 아닌 그 이상으로 연결하는 경우도 얼마든지 가능하다.
- 대각선의 __proto__를 연결해 나가기만 하면 무한대로 체인관계를 이어갈 수 있다.
- 대각선의 __proto__를 연결하는 방법은 __proto__가 가리키는 대상, 즉 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 인스턴스를 바라보게끔 하면 된다. 예제를 통해 이해해보자
- 변수 g는 Grade의 인스턴스를 바라본다. Grade의 인스턴스는 여러 개의 인자를 받아 순서대로 인덱싱하고 length가 존재하는둥 배열의 형태를 지니지만, 배열은 아닌 유사배열객체이다.
- 따라서 배열의 메서드들을 직접 쓸 수 없고, 그러기 위해서는 g.__proto__. 즉, Grade.prototype이 배열의 인스턴스를 바라보게 하면 된다.
Grade.prototype = [];
- 위 코드로 인해 분리 돼 있던 데이터가 연결되어 그림과 같이 하나의 프로토타입 체인 형태를 띠게 된다.
또한 배열의 메서드 또한 사용 가능해진 모습이다 !
'자바스크립트 > 코어 자바스크립트' 카테고리의 다른 글
코어자바스크립트 5장<클로저> (0) | 2022.08.17 |
---|---|
코어자바스크립트 4장<콜백 함수> (0) | 2022.08.14 |
코어자바스크립트 3장<this> (0) | 2022.08.13 |
코어자바스크립트 2장<실행 컨텍스트> (0) | 2022.08.12 |
코어자바스크립트 1장<데이터 타입> (0) | 2022.08.11 |