⭐️ 프로토타입
📚 Prototype [[Prototype]] Constructor
- 생성자함수(constructor)로 new 연산자로 인스턴스를 만들면, constructor의 프로토타입이라고 하는 프로퍼티의 내용이[[prototype]](프로토)라고 하는 프로퍼티로 참조를 전달
- 즉, constructor 프로토타입과 instance[[prototype]]가 곧 같은 객체를 바라봄
하지만, [[prototype]]은 접근 가능한 것이 아니라 정보를 보여주기만 할 뿐, 실제 동작상으로는 인스턴스와 동일시됨
이 그림에서 왼쪽 위에는 생성자, 오른쪽 위는 생성자의 프로토타입, 아래에는 인스턴스를 떠올리면 됨
✨ 배열의 경우
- 만약 [1, 2, 3]이라는 배열이 있다. 리터럴로 생성한 배열이든, array 생성자함수로 생성한 배열이든, 내부구조는 모두 array 생성자 함수로 생성한 것으로 동일하게 동작
- 생성자로 생성한 경우에 Array 생성자 함수는 from(), isArray(), of(), arguments, length, name, prototype 등의 프로퍼티가 있다.
- 이 중 프로토타입이라는 프로퍼티가 있는데, 이것이 배열 리터럴의 [[prototype]]로 연결되어 있음. 그리고 prototype이라는 프로퍼티는 객체인데, 이 객체 안에는 concat(), filter(), forEach(), map(), push(), pop() 등의 배열 메서드의 내용이 담겨져있다.
console.dir([1, 2, 3]); : 이번엔 배열 인스턴스를 출력하면, 크롬 개발자도구에서 이것의 [[prototype]]을 열어보면, 위에서 말한 array.prototype 안에 있는 배열의 메소드가 똑같이 담겨져 있다.
- 이중에서 prototype의 constructor이라는 프로퍼티를 열어보면, 여기에는 Array() 라는 함수가 담겨져 있다.
즉, Array.prototype.constructor에는 배열의 생성자 함수 자기 자신을 가리킨다. 여기서 똑같이 [1, 2, 3].constructor 역시 배열의 생성자 함수를 가리킨다.
즉, [1, 2, 3].constructor == [1, 2, 3].[[prototype]].constructor 과 똑같은 의미. 그리고 나아가 Array.prototype.constructor도 똑같이 Array를 가리키게 된다. 하지만, 실제로 [1, 2, 3].[[prototype]].constructor 이렇게 접근할 수 있는 건 아니고, 내부적으로 이들은 같다라는 것을 말해준다.
✨ 숫자형인 경우
만약, 숫자 10 리터럴은 어떻게 되는가
숫자 리터럴 자체는 객체가 아니므로 [[prototype]] 프로퍼티가 있을 수 없다.
그런데도, 개발자가 리터럴을 인스턴스처럼 사용하려고 하면, 예) 10.toFixed(2)
자바스크립트가 임시로 number생성자 함수로 인스턴스를 만들고, 그 프로토타입에 있는 메서드를 적용하여 원하는 결과를 얻게 한 다음에 다시 인스턴스를 제거하는 식으로 동작한다. 결과) 10.toFixed(2) -> "10.00"
✨ 문자열인 경우 (숫자형의 과정과 동일)
문자열도 마찬가지로 ('abc') 어떤 메서드를 호출하는 순간 임시로 문자열의 인스턴스를 만들어 그 메서드를 실행하고 그 결과를 얻음과 동시에 인스턴스를 다시 폐기한다.
한편 참조형 데이터들은 처음부터 인스턴스이기 때문에 메서드를 호출하는 순간 임시로 인스턴스를 생성했다가 폐기하는 과정을 거치지 않는다.
📌 정리
✅ 즉, 숫자형, 배열, 문자열, 함수든 모두 이런 식으로 메서드에 접근함 (null, undefined 제외)
✅ 데이터 자체에는 메서드가 없지만, 생성자 함수의 프로토타입 프로퍼티에 있는 것을 [[prototype]]의 연결통로에 의해 마치 자신의 것처럼 사용 가능하다!
📚 인스턴스로 부터 프로토타입 프로퍼티에 직접 접근할 수 있는 방법은 없을까?
- [[prototype]]이라고 하는 프로퍼티는 콘솔에 표시만 될 뿐 실제로 이 프로퍼티를 사용해 prototype에 직접 접근할 수는 없다.
📌 해결 방법
✅ object.getPrototypeof(instance)
✅ instance.__proto__ (가급적 사용 지양)
💻 코드 예시)
roy라는 원본과 royClone1, 2, 3, 4들은 모두 Person의 인스턴스가 된다. 왜냐하면 이것은 모두 동일한 프로퍼티에 접근할 수 있기 때문이다. 이 네개의 클론들의 네가지 방식으로 접근 가능한데, 모두 Person.prototype을 가리키고 있다.
📌 정리
✅ instance.__proto__
✅ instance
✅ Object.getPrototypeOf(instance)
✅ constructor.prototype
이 네 가지 방식으로 생성자의 prototype이라고 하는 프로퍼티에 접근 가능하다. 또한, 동일한 함수 Person이라고 하는 생성자 함수를 가리킨다.
📚 메서드 상속 및 동작원리
- Person 생성자 함수로 부터 roy 객체, jay 객체 두개의 인스턴스를 만들었다. 그리고 각각 setOlder, getAge라고 하는 메서드들을 만들었다.
- roy의 setOlder, getAge 메서드와 jay의 메서드가 동일한 내용.
📌 이럴 땐 DRY(Don't Repeat Yourself) 하자. 반복 ❌ 복붙 ❌ 최대한 반복을 줄이자
Person.prototype.setOlder = function() {
this.age += 1 ;
}
Person.prototype.getAge = function() {
return this.age;
}
위에 코드 방식을 사용한다면, 이런 식으로 인스턴스를 대량 생성 가능하다. 인스턴스들은 저마다의 고유한 정보들만 가지고 있고, 인스턴스들이 모두 똑같이 가지는 정보들은 프로토타입으로 보내면 된다. 그러면 각 인스턴스들은 마치 자신의 메서드인것처럼 사용 가능하다. 이러면, 메모리 용량 최적화도 가능하다. 또한 객체 지향 관점에서도 각각 인스턴스 고유한 정보들은 다르지만 뭉뚱그려진 특징들을 가지는 메서드들을 모두 존재하는데 이는 프로토타입으로 설명할 수 있다.
📚 프로토타입 체이닝
- 프로토타입 프로퍼티도 역시 객체
📌 초기 프로토타입 모습
그렇다면 프로토타입 프로퍼티 역시 Object 생성자 함수의 new 연산으로 생성된 인스턴스라는 말이 된다. 따라서 object의 prototype과도 연결되어 있을 수 있다.
📌 프로토타입 체이닝까지 보여진 그림
이렇게 빨간색으로 연결되어 있는 관계들을 '프로토타입 체이닝'이라고 말한다.
- 프로토타입은 모두 객체이므로, 모든 데이터 타입은 한결같이 이런 식으로 동일한 구조로 연결되어 있다.
- 숫자, 문자, 배열, 함수 모두 object.prototype과 프로토타입 체인과 연결되어 있다.
- 모든 데이터 타입에 대해 [[prototype]]으로 연결된 object.prototype에는 자바스크립트 전체를 통괄하는 공통된 메서드들인hasOwnProperty(), toString(), valueOf(), isPrototypeOf() 등의 메서드들이 존재한다. 이 메서드들은 모든 데이터타입이 프로토타입 체이닝을 통해 접근할 수 있다.
📌 객체 전용 메서드들은 따로 분리한다
- 그렇기 때문에 객체 프로토타입에는 '객체 ' 전용 메서드들을 정의할 수 없다. 객체 프로토타입에 있는 메서드들은 모든 데이터 타입에 적용되기 때문이다. (아까 언급한 hasOwnProperty(), toString(), valueOf(), isPrototypeOf() 등의 메서드들)
- 예를 들어 object.prototype에 values라는 메서드를 정의해두었다면, 객체에 대해 {a:1}.values 이렇게 동작할 수 있게 된다. 이러면, 그밖에 데이터들조차도 프로토타입 체인을 타고 Object.prototype으로 올라가 values 메서드를 실행할 수 있게 돼버린다 ( new.Date(). values, (10).values(), (false).values) 이런 식으로 모든 경우에 vlaues라는 메서드에 모두 접근 가능해진다) 이러면 객체 전용 메서드가 아니게 돼버린다.
- 그래서 values라는 메서드를 object.prototype에 정의하는 것을 포기하고, 객체 생성자 함수에 직접 메서드를 정의할 수밖에 없다.
- 유독 객체 관련한 명령어들은 객체로부터 직접 호출하는 대신 Object. 명령어를 통해 호출하며 그 매개변수로 객체 자신을 넘겨주는 방식으로 취하는 경우가 많음
// 직접 호출 불가능
obj.freeze();
obj.keys();
obj.values();
//가능
Object.freeze(obj);
Object.keys(obj);
Object.values(obj);
📌 프로토타입 체이닝의 원리를 이해하며, 프로토타입 메소드를 지워보는 실험을 해보자
만약, 배열의 인스턴스가 있다면 프로토타입을 통해 각각의 메서드로 호출 가능 ([1, 2, 3].toString())
하지만, delete Array.prototype.toString 를 하면, "[object Array]" 가 출력
또, delete Object.prototype.toString를 하고 [1, 2, 3].toString()을 호출하면 타입에러
이렇게 프로토타입 체이닝을 타고 가장 가까운 메서드부터 delete Array.prototype.toString(첫번째 삭제), delete Object.prototype.toString(두번째 삭제) 이렇게 array 프로토타입 삭제, object 프로토타입 삭제 현상이 일어나는 것.
'자바스크립트의 정석 🟡' 카테고리의 다른 글
[자바스크립트] 콜백, Promise, async/await (0) | 2024.08.12 |
---|---|
[자바스크립트] callback 정리 ✨ (0) | 2024.01.28 |
[자바스크립트] closure 정리 ✨ (0) | 2024.01.24 |
[자바스크립트] this 정리 ✨ (1) | 2024.01.23 |
자바스크립트 - Destructuring assignment (0) | 2023.09.19 |