[JS] 프로토타입

2022. 8. 8. 23:56학습/JavaScript

반응형

JS 프로토타입

프로토타입

자바스크립트는 프로토타입 기반 언어입니다.

클래스 기반 언어는 '상속'을 사용하지만 프로토타입 기반 언어는 어떤 객체를 원형을 삼고 이를 복제함으로써 상속과 비슷한 효과를 냅니다.

 


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이 나온 이유는 thischulsu.__proto__를 가리키기 떄문입니다.
    • 그렇다면 this를 인스턴스를 가리키도록 하면 정상적인 결과가 나올 것입니다.
    • 방법은 __proto__없이 사용하는 것입니다.
    
    chulsu.getName();
    
    
    • 이 방식이 되는 이유는 __proto__는 생략가능한 프로퍼티이기 떄문입니다.

정리하면

new 연산자로 Constructor를 호출하면 instance가 만들어지는데, 이 instance의 생략 가능한 프로퍼티인 __proto__Constructorprototype을 참조한다.

실제 __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()를 사용하는 걸 권장합니다.

  • 짙은색과 옅은색의 차이는 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

하지만 thischulsu.__proto__를 가리키고 그곳엔 name이 없는걸 이미 알고있습니다.

이를 call, apply를 통해서 해결이 가능합니다.


console.log(chulsu.__proto__.getName.call(chulsu)); // 철수

프로토타입 체인

prototype 객체는 '객체'입니다. ObjectObject.prototype를 갖습니다.

모든 인스턴스의 __proto__Object.prototype을 참조하게 됩니다.

ArrayArray.prototype 을 갖고있고 prototype가 객체이므로 Object.prototype를 가집니다.

__proto__는 생략이 가능하기 때문에 ArrayObject.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