프로그래밍-학습기록/Javascript

프로토타입 더 알아보기

leesche 2021. 4. 11. 23:14

🙋‍♂️프로토타입이 뭐죠?

  • 두 가지를 잘 구분해야 합니다.
    1. 내부슬롯으로서 프로토타입, [[Prototype]]
      • 프로토타입 내부슬롯은 자바스크립트 엔진에서 관리하는 객체로, 자신(인스턴스)을 생성한 생성자 함수의 prototype 프로퍼티를 가리킨다.
      • 금단의 프로퍼티, 던더프로토(__proto__)로 **[[Prototype]]**에 접근할 수 있다.
    2. 생성자 함수의 프로퍼티인 prototype
    3. 프로토타입(prototype)은 생성자 함수가 될 수 있는 모든 함수의 프로퍼티로 생기는 객체인데, 그 이름이 prototype이다.
    • 지금은 잘 이해되지 않더라도 계속 읽고 실습해보면서 둘의 차이와 관계를 알아보아요!
  • 🙋‍♂️더 자세히 말해주세요!
    • ECMAScript를 참고해볼까요?

Overview 4.3 Terms and Definitions 4.3.5 prototype 의 내용

프로토타입은 "다른 객체들을 위한 공유된 프로퍼티들을 제공하는 객체" 입니다. 생성자 함수가 객체를 만들 때, 그 객체는 내부적으로 생성자 함수의 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__) 관계

  1. 어떤 생성자 함수를 선언하면 생성자 함수프로토타입 객체가 프로퍼티로 할당된다.
  2. 어떤 생성자 함수new 연산자와 함께 호출하면
  3. 생성자 함수에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
  4. 새로운 인스턴스에는 던더프로토(__proto__)라는 프로퍼티가 자동으로 부여된다.
  5. 던더프로토생성자 함수프로토타입 프로퍼티를 참조한다.
    • 인스턴스에서 생성자 함수프로토타입 프로퍼티를 참조할 때 던더프로토작성이 생략될 수 있다.

"던더프로토"와 "내부슬롯 프로토타입"([[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 생성자 함수의 프로토타입의 프로퍼티 설명자 객체(디스크립터 객체)를 보면 ...

getOwnPropertyDescriptor 메서드가 반환하는 객체도 객체이기 때문에 던더프로토 프로퍼티가 존재한다. Object.prototype에 뭐가 들어있는지 보려면 value 프로퍼티의 값을 펼쳐봐야 한다.

  • 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가 던더프로토에 바인딩되어서 버그가 발생할 수 있습니다. 그러니 던더프로토로 접근하는 것을 권장하지 않습니다. (애초에 프로토타입내부슬롯에 직접 접근하는 것은 위험합니다)

프로토타입 체이닝

프로토타입 체인과 프로토타입 체이닝

  • 자바스크립트 엔진은 객체에서 어떤 프로퍼티나 메서드에 접근하면,
    1. 해당 객체에서 프로퍼티와 메서드를 검색합니다.
    2. 없으면, __proto__에 접근한 다음, 프로퍼티와 메서드를 검색합니다.
    3. 또 없으면, __proto__에 접근한 다음, 프로퍼티와 메서드를 ...
    4. ...
    5. 마지막까지 없으면 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를 다시 설정해줘야 한다.

메서드 오버라이드

  • 같은 이름의 메서드, 프로퍼티가 있다면 어떻게 될까?
  • 먼저 발견되는 메서드, 프로퍼티에 접근한다.

이것만 알면 끝났다🙃🥰🤩

뭐가 끝나? 실습하자...

출처