본문 바로가기

WEB/HTML CSS JS

[JS] 코어자바스크립트 ch6. 프로토타입

코어 자바스크립트 ch6 프로토타입을 읽고 정리한 글

 

목차

 

 

1. 프로토타입 개념 이해하기

  • 자바스크립트는 프로토타입 기반 언어이다.
  • 클래스 기반 언어에서는 상속을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 원형(prototype)으로 삼고 이를 복제함으로써 상속과 비슷한 효과를 얻는다.
  • ES6에 class 키워드가 추가되었지만, 그렇다고 자바스크립트가 클래스 기반언어가 된 것은 아니다.

 

아래 그림은 생성자함수와 new를 이용해 instance를 생성할 때, 생성자와 인스턴스 관계를 가장 잘 표현한 그림이다.

var instance = new Constructor();

자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성해 놓는데, 해당 함수를 생성자 함수로 사용할 경우, 그로부터 생성된 인스턴스에는 숨겨진 프로퍼티인 __proto__ 가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조합니다.

 

prototype 은 생성자함수의 프로퍼티이고, __proto__ 는 인스턴스에서 생성자의 prototype 에 접근할 수 있는 연결고리라고 생각하면 되겠다.

 

1.1 __proto__ 의 this 바인딩

Person 이라는 생성자함수의 prototype 에 getName이라는 메서드를 정의하고 사용해보는 예제

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

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


var suzi = new Person('Suzi');
//__proto__ 프로퍼티를 통해 prototype의 메서드 호출
suzi.__proto__.getName();        // undefined
  • 위 예제에서 getName() 메서드를 호출할 때 this 는 suzi 인스턴스가 아닌 suzi.__proto__ 이다.
  • suzi.__proto__ 내부에는 _name 프로퍼티가 없으므로 undefined가 반환되는 것이다.

1.2 __proto__ 는 생략 가능한 프로퍼티

  • 그런데 __proto__ 는 생략가능한 프로퍼티이다.
  • 따라서 다음과 같이 사용할 수 있다. instance에 getName() 메서드가 없다면, 생성자의 prototype 으로 올라가서 getName() 을 찾아 실행한다.
suzi.getName();		// Suzi
  • 이 때 this 는 suzi 를 바라보므로 원했던 값이 반환되는 것을 볼 수 있다.

 

2. 대표적인 내장 생성자 함수 Array

배열 인스턴스와 생성자함수를 출력해보면 위에서 설명한 관계를 직접 확인해보자.

var arr = [1, 2]
console.dir(arr);		// instance 
console.dir(Array);		// constructor

  • 생성자함수 Array 의 prototype 메서드들과 인스턴스 arr의 __proto__ 메서드가 동일함을 볼 수 있다. 프로토타입 덕분에 배열을 선언한 후에 push, pop, forEach 와 같은 메서드들을 배열에 바로 사용할 수 있는 것이다.
  • isArray, from 등 prototype 프로퍼티에 포함되어 있지 않은 메서드는 인스턴스에서 직접 호출할 수 없다.
    생성자 함수에서 직접 접근해야 한다.
var arr = [1, 2];
arr.forEach(function (){});
Array.isArray(arr);		// instance를 매개변수로 받음
isArray 를 이렇게 사용하게끔 정의한 이유
isArray는 어떤 객체가 배열 타입인지 확인하는 메서드이다. 배열뿐만 아니라 object, string, number 등 다른 자료형에 대해서도 작동해야한다. 이 메서드를 Object.prototype 에 정의해두고, Array.prototype에서 오버라이드 해서 써도 되진만, 이렇게 하면 생성자가 없는 null이나 undefined 에 대해서는 검사를 할 수 없다.(TypeError가 발생한다) 모든 자료형에 대해 사용할 수 있게 하기 위하여 Array 객체의 static method 로 정의하고, 매개변수로 검사할 대상을 넘길 수 있도록 설계한 것이다. 참고

 

3. constructor 프로퍼티

  • 생성자 함수의 프로퍼티 prototype 내부에는 constructor 라는 프로퍼티가 있다.
  • constructor 프로퍼티는 인스턴스로부터 그 원형이 뭔지 알 수 있는 수단이 된다.
  • 생성자 함수 내 prototype 의 constructor 프로퍼티는 자기 자신을 가리키고, 인스턴스 내 __proto__의 constructor 는 자신의 생성자 함수를 가리킨다.
  • 이 값은 읽기 전용 속성이 부여된 primitive type 변수를 제외하고는 값을 바꿀 수 있는데, constructor 프로퍼티가 참조하는 대상이 바뀔 뿐, 그 인스턴스가 진짜 새로운 대상의 인스턴스가 되는 게 아니므로 주의해야 한다.

3.1 다양한 constructor 접근 방법

아래 5가지의 케이스 모두 생성자 함수 Person 의 인스턴스를 생성한다. 다양한 방법으로 constructor 나 prototype에 접근할 수 있다.

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

var p1 = new Person('사람1');
var p1Proto = Object.getPrototypeOf(p1);
var p2 = new Person.prototype.constructor('사람2');
var p3 = new p1Proto.constructor('사람3');
var p4 = new p1.__proto__.constructor('사람4');
var p5 = new p1.constructor('사람5');

 

 

4. 프로토타입 체인

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

  • 인스턴스와 프로토타입에 같은 이름으로 정의된 메서드가 있다면, 인스턴스 자체  메서드가 우선권을 갖는다.
  • instance.__proto__.method명 이렇게 프로토타입의 메서드에 접근하는 것도 가능.

 

4-2. 프로토타입 체인

console.dir([1, 2]);

개발자 도구에서 배열 리터럴의 __proto__를 확인해보면 __proto__ 가 또 있는 것을 확인할 수 있다. 이는 Array의 prototype이 Object이기 때문이다. 어떤 생성자 함수이든 prototype은 반드시 객체이다. 어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인 이라고 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝 이라고 한다.

var arr = [1, 2];
arr(.__proto__).push(3);
arr(.__proto__)(.__proto__).hasOwnProperty(2);
  • __proto__ 는 생략할 수 있으므로 프로토타입 체이닝에 의해 배열 인스턴스에서도 Object의 prototype 메서드를 바로 사용할 수 있다.

 

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

  • 어떤 생성자 함수이든 prototype은 반드시 객체이다.
  • 따라서 Object.prototype은 언제나 프로토타입 체인의 최상단에 존재하게 된다.
  • 따라서 Object.prototype에 있는 메서드는 어느 자료형이던 인스턴스에서 바로 호출할 수 있다.
  • 따라서 객체에서만 사용할 메서드는 prototype 안에 정의하지 않는다. 객체에서만 사용할 메서드를 prototype 내부에 정의하면 다른 데이터타입도 프로토타입 체인을 타고 올라와서 해당 메서드를 사용할 수 있게 되기 때문이다.
  • 이런 메서드를 Static method라 한다.

 

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

  • 프로토타입 체인을 2단계 이상으로 연결하고 싶다면? 
__proto__가 가리키는 대상, 즉 생성자 함수의 prototype이
연결하고자 하는 상위 생성자 함수의 인스턴스를 바라보게끔 해주면 된다.
var Grade = function () {
  var ars = 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의 인스턴스 g 는 유사배열객체이다. g { 0: 100, 1: 80, length: 2 }

따라서 배열 메서드를 바로 사용할 수 없다. 이를 가능하게 하고 싶다면,

인스턴스 g의 __proto__, 즉 Grade.prototype 이 배열의 인스턴스를 바라보게 해주면 된다.

Grade.prototype = [];

이젠 쓸 수 있다.

g.pop();
g.push(90);

 

참고

'WEB > HTML CSS JS' 카테고리의 다른 글

[JS] 비동기 처리 - Callback, Promise, async/await  (0) 2021.07.24
Web 공부 리소스  (6) 2021.07.10
[JS] 코어자바스크립트 ch3. this  (0) 2021.07.03
[JS] 화살표함수  (0) 2021.07.02
[JS] 함수 선언문과 함수 표현식  (0) 2021.05.28