2022. 8. 8. 23:56ㆍ학습/JavaScript
프로토타입
자바스크립트는 프로토타입 기반 언어입니다.
클래스 기반 언어는 '상속'을 사용하지만 프로토타입 기반 언어는 어떤 객체를 원형을 삼고 이를 복제함으로써 상속과 비슷한 효과를 냅니다.
var instance = new Constructor();
new
연산자와 생성자 함수를 호출하면- 생성자 함수(
Constructor
)에 정의된 내용을 바탕으로 인스턴스가 생성됩니다. - 인스턴스에는
__proto__
라는 프로퍼티가 자동으로 부여됩니다. __proto__
는 생성자 함수(Constructor
)의prototype()
이라는 프로퍼티를 참조합니다.
__proto__
와 prototype
은 프로토타입의 핵심 개념입니다.
prototype
객체 내부에는 인스턴스가 사용할 메서드를 저장하며 인스턴스가 __proto__
를 통해서 메서드에 접근을 합니다.
var Person = function (name) {
this._name = name;
};
Person.prototype.getName = function() {
return this._name;
};
var chulsu = new Person ('Chulsu');
chulsu.__proto__.getName(); // undefined
chulsu.__proto__.getName();
결과가 에러가 아닌undefined
가 나왔다는 것은 호출할 수 있는 함수라는 것을 의미합니다.undefined
이 나온 이유는this
가chulsu.__proto__
를 가리키기 떄문입니다.- 그렇다면
this
를 인스턴스를 가리키도록 하면 정상적인 결과가 나올 것입니다. - 방법은
__proto__
없이 사용하는 것입니다.
chulsu.getName();
- 이 방식이 되는 이유는
__proto__
는 생략가능한 프로퍼티이기 떄문입니다.
- 그렇다면
정리하면
new
연산자로 Constructor
를 호출하면 instance
가 만들어지는데, 이 instance
의 생략 가능한 프로퍼티인 __proto__
는 Constructor
의 prototype
을 참조한다.
실제 __proto__
와 prototype
var Constructor = function (name) {
this.name = name;
};
Constructor.prototype.method1 = function() {};
Constructor.prototype.property1 = 'Constructor Prototype Property';
var instance = new Constructor('Instance');
console.dir(Constructor);
console.dir(instance);
- 왜
__proto__
가 없고[[Prototype]]
가 있을까요...- 사실 ES5.1 명세에서
__proto__
라는 프로퍼티는[[Prototype]]
이라는 명칭으로 정의돼 있기 때문입니다.- 그냥 같은 개념이 아닐까 싶습니다.
[[Prototype]]
,__proto__
내부 슬롯에는 직접 접근이 불가합니다.- 모든 객체는
__proto__
를 통해 자신의 프로토타입([[Prototype]]
내부 슬롯)에 접근할 수 있으나 권장하지 않습니다.- 대신
Object.getPrototypeOf()
,Object.create()
를 사용하는 걸 권장합니다.
- 대신
- 사실 ES5.1 명세에서
- 짙은색과 옅은색의 차이는
enumerable
속성의 차이입니다.- 짙은색은 열거가 가능한 프로퍼티이며
- 옅은색은 열거할 수 없는 프로퍼티입니다.
for in
등으로 객체에 접근시 열거가 되고 안되고가 구분됩니다.
var arr = [1, 2];
console.dir(arr);
console.dir(Array);
Array
의 프로퍼티에from()
,isArray()
등을 제외한Prototype
만은 공유하고 있는 것을 볼 수 있습니다.- 인스턴스의
__proto__
는Array.prototype
을 참조하지만 그외의 메서드들은 직접 호출할 수가 없습니다.- 그 외의 메서드를 호출하기 위해서는 직접 접근해야합니다.
var arr = [1, 2];
Array.isArray(arr);
constructor 프로퍼티
생성자 함수의 프로퍼티인prototype
, 인스턴스의 __proto__
객체 내부에는 constructor
라는 프로퍼티가 있습니다.
이 프로퍼티는 원래의 생성자 함수(자기 자신)를 참조합니다.
왜냐하면 인스턴스로부터 그 원형이 무엇인지 알 수 있는 수단이기 때문입니다.
var arr = [1, 2];
Array.prototype.constructor === Array // true
arr.__proto__.constructor === Array // true
arr.constructor === Array // true
var arr2 = new arr.constructor(3,4);
console.log(arr2) //[3,4]
이처럼 인스턴스에서 constructor
에 접근이 가능합니다.
그런데 기본형 리터럴 변수(number
, string
, boolean
)를 제외하고 constructor
변경이 가능합니다.
- 기본형 리터럴 변수(
number
,string
,boolean
)는 읽기 전용 속성이 부여되어 있기 때문입니다.
그렇다고 constructor
변경이 된다고해서 원형이 바뀌지는 않습니다.
결국 인스턴스의 생성자의 정보를 얻기위해 constructor
프로퍼티에 의존하는 것은 좋지 않습니다.
하지만 이 방식을 통해서 클래스 상속을 흉내내기도 합니다.
이들은 동일한 대상을 가리킵니다.
[Constructor]
[instance].__proto__.constructor
[instance].constructor // __proto__ 생략가능
Object.getPrototypeOf([instance]).constructor
[Constructor].prototype.constructor
프로토타입 체인
메소드 오버라이드
인스턴스가 __proto__
에 있는 프로퍼티와 동일한 이름의 프로퍼티 또는 메서드를 가지고 있는 상황이 있을 수 있습니다.
이러한 현상을 메서드 오버라이드라고 합니다.
이 경우 자바스크립트 엔진이 name
이라는 메서드를 찾기위해 자신의 프로퍼티를 검색하고 그 다음 __proto__
를 검색합니다.
var Person = function (name) {
this.name = name;
};
Person.prototype.getName = function () {
return this.name;
};
var chulsu = new Person('철수');
chulsu.getName = function () {
return '김' + this.name;
};
console.log(chulsu.getName()); // 김철수
그럼 한번 __proto__
에 직접 접근을 해보겠습니다.
console.log(chulsu.__proto__.getName()); // undefined
하지만 this
가 chulsu.__proto__
를 가리키고 그곳엔 name
이 없는걸 이미 알고있습니다.
이를 call
, apply
를 통해서 해결이 가능합니다.
console.log(chulsu.__proto__.getName.call(chulsu)); // 철수
프로토타입 체인
prototype
객체는 '객체'입니다. Object
는 Object.prototype
를 갖습니다.
모든 인스턴스의 __proto__
는 Object.prototype
을 참조하게 됩니다.
Array
는 Array.prototype
을 갖고있고 prototype
가 객체이므로 Object.prototype
를 가집니다.
__proto__
는 생략이 가능하기 때문에 Array
는 Object.prototype
의 메소드도 사용이 가능해집니다.
이처럼 어떤 데이터의 __proto__
프로퍼티 내부에서 다시 __proto__
프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라고 합니다. 그리고 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 합니다.
그러면 프로토타입 체이닝의 끝에는 무조건 Object.prototype
이 있을거라는 추측이 가능해집니다.
그뿐만 아니라 생성자 함수는 모두 함수이기 때문에 Function
생성자 함수의 prototype
과도 연결됩니다.
Function
생성자 함수도 함수이기 때문에 Function
생성자 함수의 prototype
과 연결 ... (반복)
이처럼 __proto__
의 constructor
의 __proto__
의 constructor
의 __proto__
의 constructor
...
재귀적으로 반복하는 구성을 갖고 있습니다. 그런데 이들은 같은 Function
생성자 함수를 가리키기 때문에 메모리가 낭비되지는 않습니다.
객체 전용 메서드의 예외사항
prototype
이 객체이기 때문에 메서드를 따로 Object.prototype
에 정의한다면 다른 데이터타입도 해당 메서드를 사용하게 됩니다.
그래서 객체 전용메서드들은 부득이하게 Object.prototype
이 아닌 Object
에 스태틱 메서드로 부여할 수밖에 없었습니다.
또한 생성자 함수인 Object
와 인스턴스인 객체 리터럴 사이에는 this
를 통한 연결이 불가능하기 때문에 대상 인스턴스를 인자로 직접 주입하는 방식으로 구현돼 있습니다.
다중 프로토타입 체인
두 단계 이상의 체인을 지니는 다중 프로토타입 체인도 가능합니다.
이 방법을 통해서 클래스와 비슷하게 동작하는 구조를 만들 수 있습니다.
방법은 프로토타입에 인스턴스를 바라보게 하면 됩니다. 그럼 해당 인스턴스로 프로토타입 체이닝이 일어납니다.
이 글은 "코어자바스크립트" 내용에 기반하여 작성한 글입니다.
잘못된 부분이나 이해가 잘못된 부분에 대해서는 댓글로 써 주시면 감사하겠습니다!
'학습 > JavaScript' 카테고리의 다른 글
[JS] 클래스 (0) | 2022.08.11 |
---|---|
[JS] 클로저 (0) | 2022.08.07 |
[JS] 콜백함수 (0) | 2022.08.07 |
[JS] this (0) | 2022.08.06 |
[JS] 실행 컨텍스트 (0) | 2022.07.31 |