자바스크립트의 프로토타입

이희제·2024년 4월 29일
post-thumbnail

코어 자바스크립트 6장을 통해 프로토타입에 대해 알아보자.

자바스크립트는 프로토타입 기반 언어이다. 어떤 객체를 원형으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻는다.

1. 프로토타입의 개념 이해

1-1. constructor, prototype, instance

프로토타입 도식은 다음과 같다.

var instance = new Constructor();

위 예시 코드를 도식에 맞춰 설명하면 다음과 같다.

  1. 생성자 함수(Constructor)를 new 연산자와 함께 호출
  2. 새로운 instance 생성
  3. 해당 instance에 __proto__ 프로퍼티 자동 부여
  4. __proto__는 생성자 함수(Constructor)의 prototype 프로퍼티 참조

prototype과 __proto__ 의 관계가 프로토타입 개념의 핵심이다.

prototype 내부에는 인스터스가 사용할 메서드를 저장하는데, 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 이 메서드들에 접근할 수 있게 된다.

__proto__ => prototype => 메서드

예시를 통해 살펴보자.

var Person = function(name) {
  this._name = name;
};
Person.prototype.getName = function() {
  return this._name;
};

Person 인스턴스는 __proto__ 를 통해 getName을 호출할 수 있다. (__proto__가 Person의 prototype을 바라보고 있기 때문이다.)

var Person = function(name) {
  this.name = name;
};
Person.prototype.getName = function() {
  return this.name;
};

var heeje = new Person('heeje');
console.log(heeje.getName()); // heeje

__proto__는 생략 가능한 프로퍼티이다.

프로토타입 개념에 대한 상세한 설명

  • 자바스크립트는 함수에 자동으로 prototype 프로퍼티(객체) 생성
  • 함수를 생성자 함수로서 사용할 경우 인스터스에는 __proto__ 프로퍼티가 자동 생성되고 생성자 함수의 prototype 프로퍼티 참조

    따라서, 인스터스에서 생성자 함수의 prototype 내 메서드, 프로퍼티에 접근할 수 있게 된다.

내장 생성자 함수 Array 예시)

__proto__ 는 현재 deprecated 되었다. (MDN, 직접 접근하지 말자)

1-2. constructor 프로퍼티

생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 존재한다.

constructor는 원래의 생성자 함수(자기 자신)를 참조한다.

constructor를 통해 인스턴스로부터 원형이 무엇인지 알 수 있다.

var arr = [1, 2];
Array.prototype.constructor === Array; // true
arr.__proto__.constructor === Array; // true
arr.constructor === Array; // true __proto__ 생략

var arr2 = new arr.constructor(3, 4);
console.log(arr2); // [3, 4]

[다양한 constructor 접근 방법]

var Person = function(name) {
  this.name = name;
};
var p1 = new Person('사람1'); // Person { name: "사람1" } true
var p1Proto = Object.getPrototypeOf(p1);
var p2 = new Person.prototype.constructor('사람2'); // Person { name: "사람2" } true
var p3 = new p1Proto.constructor('사람3'); // Person { name: "사람3" } true
var p4 = new p1.__proto__.constructor('사람4'); // Person { name: "사람4" } true
var p5 = new p1.constructor('사람5'); // Person { name: "사람5" } true

[p1, p2, p3, p4, p5].forEach(function(p) {
  console.log(p, p instanceof Person);
});

Object.getPrototypeOf()를 통해 자신이 바라보고 있는 prototype를 가지고 올 수 있다.


2. 프로토타입 체인

2-1. 메서드 오버라이드

인스터스가 참조하고 있는 prototype 객체 내의 메서드 및 프로퍼티를 동일한 이름으로 가지고 있으면 어떻게 될까?

예시 코드를 통해 살펴보자.

var Person = function(name) {
  this.name = name;
};
Person.prototype.getName = function() {
  return this.name;
};

var iu = new Person('지금');
iu.getName = function() {
  return '바로 ' + this.name;
};
console.log(iu.getName()); // 바로 지금

iu 객체 내에 있는 getName 메서드가 호출되는 것을 확인할 수 있다.

이는 메서드 오버라이드가 발생해서 그렇다. 기존 메서드 위에 메서드를 덮어 씌운 것이다.

자바스크립트 엔진이 getName 메서드를 찾는 방식은 다음과 같다.

  1. 자신의 프로퍼티 검색 후 있으면 해당 메서드 호출
  2. 없다면 __proto__ 에서 검색이 있다면 메서드 호출

Person의 prototype 내 getName은 유지되는 것을 확인할 수 있다.

2-2. 프로토타입 체인

__proto__ 내에 __proto__ 가 등장하는 것을 확인할 수 있다.

모든 객체의 __proto__에는 Object.prototype이 연결된다. (prototype이 객체이기 때문이다.)

Array 객체를 예시로 든 최상위 객체 Object와의 구조 도식

따라서 배열은 Object.prototype 내부의 메서드를 호출하여 사용할 수 있다.

var arr = [1, 2];
arr.push(3);
arr.hasOwnProperty(2); // true

이처럼 __proto__ 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라고 한다.

그리고 이 체인을 따라 검색하는 것을 프로토타입 체이닝이라고 한다.

2-3. 객체 전용 메서드의 예외사항

어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재한다.

따라서 객체에서만 사용할 메서드를 Obejct.prototype 내부에 정의하면 다른 데이터 타입도 해당 메서드에 접근해 사용할 수 있게 된다.

Object.prototype.getEntries = function() {
  var res = [];
  for (var prop in this) {
    if (this.hasOwnProperty(prop)) {
      res.push([prop, this[prop]]);
    }
  }
  return res;
};
var data = [
  ['object', { a: 1, b: 2, c: 3 }], // [["a",1], ["b", 2], ["c",3]]
  ['number', 345], // []
  ['string', 'abc'], // [["0","a"], ["1","b"], ["2","c"]]
  ['boolean', false], // []
  ['func', function() {}], // []
  ['array', [1, 2, 3]], // [["0", 1], ["1", 2], ["2", 3]]
];
data.forEach(function(datum) {
  console.log(datum[1].getEntries());
});

객체만을 대상으로 동작하는 Object 전용 메서드들은 부득이 Object.prototype이 아닌 Object에 정적(static)메서드로 구현되어 있다.

반대로, Object.prototype에는 어떤 데이터에서도 활용할 수 있는 범용적인 메서드들만 있다.

toString, hasOwnProperty, valueOf, isPrototypeOf 등은 모든 instance가 직접 호출할 수 있다.

예외로 Object.create(null)는 __proto__ 가 없는 객체를 생성한다.

2-4. 다중 프로토타입 체인

자바스크립트의 기본 내장 데이터 타입들은 모두 프로토타입 체인 1단계(객체), 2단계(나머지)로 끝나는 경우가 있지만 사용자가 새롭게 만드는 경우 계속해서 단계를 추가할 수 있다.

__proto__를 연결하는 방법은 __proto__가 가리키는 대상을 생성자 함수의 prototype이 연결하고자 하는 상위 생성자 함수의 instance를 지정해 주면된다.

var Grade = function() {
  var args = Array.prototype.slice.call(arguments);
  for (var i = 0; i < args.length; i++) {
    this[i] = args[i];
  }
  this.length = args.length;
};
var g = new Grade(100, 80)
  • Grade의 instance는 여러개의 인자를 받아 각 순서대로 인덱싱해서 저장한다. (유사배열객체)
    • 유사배열객체이므로 배열의 메서드는 사용할 수 없다.
  • 변수 g는 Grade의 instance를 바라본다.
  • g.__proto__ 즉, Grade.prototype이 배열의 instance를 바라보게 해주면 배열의 메서드를 사용할 수 있다.
    • Grade.prototype = [];
    • Grade의 prototype에 빈 배열을 할당하면 Grade의 instance인 g에서 직접 배열 메서드를 사용할수 있게된다.
    • g instance는 프로토타입 체인을 따라 Grade.prototype, Array.prototype, Object.prototype에 접근할 수 있는 3단계 다중 프로토타입 체인 형식이 된다.

스코프 체인과 프로토타입 체인의 관계에 대해...
https://homoefficio.github.io/2016/01/16/JavaScript-%EC%8B%9D%EB%B3%84%EC%9E%90-%EC%B0%BE%EA%B8%B0-%EB%8C%80%EB%AA%A8%ED%97%98/

profile
그냥 하자

0개의 댓글