🙋♂️프로토타입이 뭐죠?
- 두 가지를 잘 구분해야 합니다.
- 내부슬롯으로서 프로토타입, [[Prototype]]
- 프로토타입 내부슬롯은 자바스크립트 엔진에서 관리하는 객체로, 자신(인스턴스)을 생성한 생성자 함수의 prototype 프로퍼티를 가리킨다.
- 금단의 프로퍼티, 던더프로토(__proto__)로 **[[Prototype]]**에 접근할 수 있다.
- 생성자 함수의 프로퍼티인 prototype
- 프로토타입(prototype)은 생성자 함수가 될 수 있는 모든 함수의 프로퍼티로 생기는 객체인데, 그 이름이 prototype이다.
- 지금은 잘 이해되지 않더라도 계속 읽고 실습해보면서 둘의 차이와 관계를 알아보아요!
- 내부슬롯으로서 프로토타입, [[Prototype]]
- 🙋♂️더 자세히 말해주세요!
- ECMAScript를 참고해볼까요?
프로토타입은 "다른 객체들을 위한 공유된 프로퍼티들을 제공하는 객체" 입니다. 생성자 함수가 객체를 만들 때, 그 객체는 내부적으로 생성자 함수의 prototype 프로퍼티를 참조합니다. 왜 그럴까요? 프로퍼티 참조 해결(=상속을 통한 효율, 즉 프로토타입 체이닝)의 목적을 위해서죠!
프로토타입은 "다른 객체들을 위한 공유된 프로퍼티들을 제공하는 객체" 입니다. 생성자 함수가 객체를 만들 때, 그 객체는 내부적으로 생성자 함수의 prototype 프로퍼티를 참조합니다. 왜 그럴까요? 프로퍼티 참조 해결(=상속을 통한 효율, 즉 프로토타입 체이닝)의 목적을 위해서죠!
🙋♂️함수를 선언하면 어떤 일이 일어날까요?
- 생성자 함수가 될 수 있는 함수를 선언하면 prototype 객체가 함수 객체의 프로퍼티로 자동으로 생깁니다.
- 참고! 생성자 함수가 될 수 없는 함수는 화살표 함수로 선언된 함수이다.
🙋♂️새로 생긴 프로토타입에는 무엇이 담길까?
- constructor와 __proto__(던더프로토)가 담긴다.
- a.prototype.constructor
- 함수 a를 가리킨다. 즉, 생성자 함수의 프로토타입의 constructor(생성자)는 생성자 함수 본인이다!
- a.prototype.__proto__
- 더블 언더스코어 프로토(double underscore proto)를 줄여 **던더 프로토(dunder proto)**라고 불린다.
- 숨겨진 비밀의 Secret 객체 ...
- 사실 던더 프로토는 원래 개발자가 자바스크립트 엔진에서만 쓰는 [[Prototype]] (프로토타입 내부 슬롯)을 구현한 접근자 프로퍼티이다.
- 즉, 던더 프로토로 [[Prototype]](프로토타입 내부 슬롯)에 간접 접근할 수 있다.
- 그렇다면 프로토타입 내부 슬롯에는 무엇이 담길까? 콘솔에 출력시켜 보자!
- anonymous 라는 이름의 함수를 가리키고 있다. 말그대로 정체불명의 익명 함수인 것일까?
- 사실 이 익명 함수는 함수를 생성하는 생성자 함수인 Function 함수를 가리킨다.
- 즉 다음과 같다.
- console.log(a.__proto__ === Function.prototype); // true
- 어? 그런데 함수 a의 던더프로토인 Function 함수도 던더프로토를 가지고 있다!
- 이쯤되면 기억이 희미해질 때가 됐다. 던더프로토는 무얼 가리킨다고 했을까?
- 프로토타입 내부 슬롯([[Prototype]])을 가리킨다.
- 내부 슬롯 프로토타입이 도대체 뭔데??!!!
- 그러니까 ...
- 프로토타입 내부 슬롯이 담고 있는 객체. 그것은 바로 ...
- 이제 허구한 날 떠올리게 될 프로토타입 도식
- "자신을 생성한 생성자 함수의 프로토타입 객체"입니다.
생성자(constructor), 프로토타입(prototype),
인스턴스(instance) 그리고 던더프로토(__proto__) 관계
- 어떤 생성자 함수를 선언하면 생성자 함수에 프로토타입 객체가 프로퍼티로 할당된다.
- 어떤 생성자 함수를 new 연산자와 함께 호출하면
- 생성자 함수에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
- 이 새로운 인스턴스에는 던더프로토(__proto__)라는 프로퍼티가 자동으로 부여된다.
- 이 던더프로토는 생성자 함수의 프로토타입 프로퍼티를 참조한다.
- 이 인스턴스에서 생성자 함수의 프로토타입 프로퍼티를 참조할 때 던더프로토는 작성이 생략될 수 있다.
"던더프로토"와 "내부슬롯 프로토타입"([[Prototype]])은 같은 것을 가리킨다 것을 꼭 기억해두세요. 아니면 나중에 헷갈려요!
- 던더프로토, 프로토타입 내부슬롯[[Prototype]], 프로토타입(흔히 이렇게 말해서 객체.prototype과 헷갈린다)은 같은 객체를 말합니다.
던더프로토는 표준이 아니었다가 표준이 됐지만 권장하지 않아요!
- ES5까지 던더프로토는 표준이 아니었다. Object.getPrototypeOf(instance) 또는 Reflect.getPrototypeOf(instance) 메서드를 사용해 생성자 함수의 프로토타입에 접근하도록 명세에 정해놓았었다. 하지만 일부 브라우저에서 던더프로토를 구현해놓아 브라우저 간 호환성을 고려해 ES6(ES2015)부터 표준으로 채택했다.
- 모든 객체가 던더 프로토 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에 사용을 권장하지 않는다. 프로토타입에 접근하고 싶을 경우 위에 언급된 Object.getPrototypeOf(instance) 메서드를 사용하고, 프로토타입을 교체하고 싶을 경우, Object.setPrototypeOf(instance)를 사용하는 것을 권장한다.
왜 생성자 함수에는 자동으로 prototype이라는 프로퍼티가 생길까?
- Function 인스턴스의 prototype을 설명하는 ECMAScript 명세를 살펴보면 ...
생성자로서 사용될 수 있는 Function 인스턴스는 prototype 프로퍼티를 가진다.
고 합니다.
- 그렇습니다. ECMAScript에 그렇게 쓰여있어서 ... 자바스크립트 엔진이 그렇게 구현했을 뿐입니다.
생성자 함수도 객체다! 그리고 객체는 객체 생성자 함수로 생성된다.
- 생성자 함수도 함수이고, 함수는 객체이기 때문에 객체 생성자 함수에 의해 생성된 인스턴스이다.
- 따라서 프로토타입 체인의 최상위에는 객체 생성자 함수의 프로토타입(Object.prototype)이 있다.
- 그렇다면 객체 생성자 함수 Object의 던더프로토는 있을까? 없을까?
- 있다. 객체 생성자 함수도 함수이기 때문이다. 객체 생성자 함수의 던더프로토는 Function이다.
- Object.prototype의 던더프로토는 무엇일까?
null 이다.
- ECMAScript 명세에서 Object.prototype 객체에 대한 명세를 보면,
- "값이 null인 프로토타입 내부슬롯을 가지고 있다"고 한다.
- Object 생성자 함수의 프로토타입의 프로퍼티 설명자 객체(디스크립터 객체)를 보면 ...
- Object.prototype에는 던더프로토 프로퍼티가 없다!
- mdn 문서를 보면 더 이상 지원되지 않는다고 쓰여 있다.사실 Object.prototype.__proto__만이 아니라, 즉 자바스크립트의 모든 객체에서 던더프로토로 프로토타입 내부슬롯사실 Object.prototype.__proto__만이 아니라, 즉 자바스크립트의 모든 객체에서 던더프로토로 프로토타입 내부슬롯에 접근하지 말라는 것과 같다.
- 보통 없는 프로퍼티에 접근하면 undefined를 출력하지만 null이 출력되는 것으로 보아 자바스크립트 엔진에서 모종의 이유(호환성 문제)로 따로 그렇게 (ECMAScript 명세에 나와 있는 것처럼) 명시적으로 null이 반환되도록 구현했을 것이라 예상된다.
그래서 그래서 그래서?
- 우리가 할 수 있는 게 뭘까?
- 대표적으로 상속이 있다.
던더프로토는 생략 가능하도록 구현되어 있다는 것을 기억하기!
- 연습 문제: (1)과 (2)의 출력 값은 무엇일까요?
var Person = function (name) {
this.name = name;
};
Person.prototype.getName = function () {
return this.name;
};
var choiJun= new Person("Choi Jun");
choiJun = {name: "Choi Jun"}
console.log(choiJun.__proto__.getName()); // (1)
console.log(choiJun.getName()); // (2)
- 생략 가능하기 때문에 생성자 함수의 프로토타입 프로퍼티를 마치 자신의 프로퍼티처럼 접근할 수 있습니다.
- 하지만 던더프로토로 프로토타입 내부슬롯에 접근하여 메서드를 호출할 경우, this가 던더프로토에 바인딩되어서 버그가 발생할 수 있습니다. 그러니 던더프로토로 접근하는 것을 권장하지 않습니다. (애초에 프로토타입내부슬롯에 직접 접근하는 것은 위험합니다)
프로토타입 체이닝
프로토타입 체인과 프로토타입 체이닝
- 자바스크립트 엔진은 객체에서 어떤 프로퍼티나 메서드에 접근하면,
- 해당 객체에서 프로퍼티와 메서드를 검색합니다.
- 없으면, __proto__에 접근한 다음, 프로퍼티와 메서드를 검색합니다.
- 또 없으면, __proto__에 접근한 다음, 프로퍼티와 메서드를 ...
- ...
- 마지막까지 없으면 undefined를 반환합니다.
- 이 프로토타입 체이닝은 상속 구현의 기반이 됩니다.
상속 구현하는 방법
- 프로토타입 교체하기
- 생성자 함수의 프로토타입 직접 교체하기
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
walk: function () {
console.log(`우리 ${this.name} 잘 걷는다`);
}
};
return Person;
}());
Person.prototype.constructor = Person;
const baby = new Person("막둥이");
baby.walk();
- 인스턴스의 던더프로토로 생성자 함수의 프로토타입 접근하여 교체하기
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
walk: function () {
console.log(`우리 ${this.name} 잘 걷는다`);
}
};
return Person;
}());
Person.prototype.constructor = Person;
const baby = new Person("막둥이");
baby.walk();
- 이렇게 하면, Person의 새로운 프로토타입(newPrototype)의 constructor 프로퍼티가 Person를 가리키게 되지 않는다. 따로 constructor가 기존의 생성자 함수를 바라보도록 다시 설정을 해줘야 한다.
번거롭다. 따라서 이런 방법은 권장되지 않는다.
- 직접 상속하기
- Object.create(프로토타입이될객체)는 프로토타입이될객체가 프로토타입인 빈 객체를 반환한다.
놀이기구.prototype = Object.create(시설.prototype);
- 번거롭다. constructor를 다시 설정해줘야 한다.
메서드 오버라이드
- 같은 이름의 메서드, 프로퍼티가 있다면 어떻게 될까?
- 먼저 발견되는 메서드, 프로퍼티에 접근한다.
이것만 알면 끝났다🙃🥰🤩
뭐가 끝나? 실습하자...
출처
- 책 <모던 자바스크립트 Deep Dive>
- 책 <코어 자바스크립트>
- https://262.ecma-international.org/11.0/#sec-properties-of-the-object-prototype-object
- https://262.ecma-international.org/#sec-terms-and-definitions-prototype
- https://developer.mozilla.org/ko/docs/conflicting/Web/JavaScript/Reference/Global_Objects/Object
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
'프로그래밍-학습기록 > Javascript' 카테고리의 다른 글
클래스 리스트 순서에 따른 구현은 위험하다! (2) | 2021.04.01 |
---|---|
Javascript를 배우기에 알면 좋을 배경 지식들 1 (ECMAScript, 자바스크립트 엔진, 런타임) (0) | 2021.03.26 |
조건문에 변수를 넣을 때 주의하자 (0) | 2021.03.16 |
배열을 같은 값으로 초기화할 때 하드코딩보다 Array(number).fill(something)를 사용하자. (0) | 2021.03.12 |
Array 메소드(filter, find 등)의 반환 값을 잘 알아두자. (0) | 2021.03.11 |