Prototype(프로토타입)

MaxlChan·2020년 7월 7일
1

자바스크립트는 프로토타입 기반 언어라는 점에서 다른 컴퓨터 언어와 차별점은 둔다고 한다. 즉, 프로토타입에 대한 개념 이해 없이는 자바스크립트에 대한 근본적인 이해를 할 수 없다고 생각하기에 배운 것을 정리하는 겸 포스팅 해보고자한다.

🚨 올바르지 않은 내용이 있을 경우 댓글로 남겨주시면 감사드리겠습니다.

Prototype 이란?

흔히 일반적으로 프로토타입이라는 단어는 어떤 회사에서 시제품이 나오기 전 먼저 제품을 검증하기 위해 먼저 일단 세상밖으로 나오는 시제품의 '원형'이라고 받아들여진다.

자바스크립트에서 Prototype이라는 개념도 일반적으로 이해되는 개념과 크게 다르지 않다고 생각한다(그러니깐 단어가 같겠지?)

뒤에서 자세히 설명하겠지만 간단히 요약을 하자면,

자바스크립트에서 모든 객체들은 그 해당 객체의 '프로토타입'을 가진다.

모든 객체는 자신이 가진 속성과 메소드 외에, 프로토타입이 되는 객체로부터 속성과 메소드를 상속받아 마치 자신의 속성과 메소드인 것처럼 접근할 수 있게 된다.(상속이란 단어는 오해의 소지가 있으므로 후반부에 다시 정리하도록 하겠다.)

객체의 '프로토타입' 또한 (객체이기 때문에) 본인의 '프로토타입'을 갖는다.
이렇게 계속해서 체인(프로토타입 체인이라고 부른다)을 타고 올라가다 보면
'해당 프로토타입 객체의 프로토타입 객체는 없습니다' 라고 정의되는
null을 도출하는 Object.prototype에서 프로토타입 체인이 종결된다.

최대한 몇 문장으로 요약하여 설명해보려고했지만 역시 자바스크립트에서 Prototype은 ~~다! 라고 짧게 설명하는 것은 쉽지 않다. 우선 객체가 어떻게 생성되는가 에서부터 살펴볼 필요가 있다.

'new' keyword

함수는 new 키워드와 함께 호출지되는지 여부에 따라 일반 함수(임의로 이름을 붙혔다)와 생성자 함수로 구분할 수 있다.

// 일반 함수
function callRegularFunc() {
  const a = "Hi I'm regular function!";
  
  return a;
}

const returnValue = callRegularFunc();  //일반 함수로 사용
console.log(returnValue);  // "Hi I'm regular function!"
                                       

// 생성자 함수
function Location(country, latitude, longitude) {
  this.country = country;
  this.latitude = latitude;
  this.longitude = longitude;
}

Location.prototype.callMyLatitude = function() {
  console.log(`My latitude is ${this.latitude}`);
}

const southKorea = new Location("SouthKorea", 33, 124);  // 생성자 함수로 사용

console.log(southKorea); // Location {country: "SouthKorea", latitude: 33, longitude: 124}

일반 함수

용도 - 함수 안 특정 과정을 수행하기 위함
반환값 - 함수 내부 return 값을 반환
Naming convention - 첫 단어가 소문자, 주로 동사
호출방법 - 함수 이름 뒤에 ()를 붙혀서 호출

생성자 함수

용도 - 특정 객체를 만들기 위함
반환값 - 새로운 객체(함수 내부의 this는 새로운 빈 객체를 의미)
Naming convention - 첫 단어가 대문자, 주로 명사
호출방법 - 함수 이름 뒤에는 (), 앞에 new라는 키워드와 함께 호출

The new keyword does the following things:

  1. Creates a blank, plain JavaScript object;
  2. Links (sets the constructor of) this object to another object;
  3. Passes the newly created object from Step 1 as the this context;
  4. Returns this if the function doesn't return an object.
    출처 - MDN

위와 같이 함수는 new라는 키워드와 함께 선언되었는지 여부에 따라 일반함수와 생성자 함수로 나뉘고 생성자 함수는 새로운 객체를 만들어서 반환한다.(return 값이 없더라도)

즉 변수 southKorea에는 new Location("SouthKorea", 33, 124)이라는 생성자 호출을 통해 생성된 객체가 할당되고 변수 southKoreaLocation 생성자 함수의 인스턴스라고 불리운다. 그 내용은 아래와 같다.

사실상 지금껏 객체 리터럴 방식으로 편리하게 만들었던 객체(배열, 함수, 객체)들 또한
new 키워드 + 생성자 함수의 선언으로 만들어진 객체(인스턴스)이다.

let a = [];
let b = function() {};
let c = {};

모든 함수는 prototype 속성을 갖는다.

선언되고 생성된 목적에 상관없이 모든 함수는 prototype 속성을 갖는다.

하지만 일반 함수로 호출되고 사용되었을 경우 이 prototype 속성은 그다지 큰 의미를 갖지 않는 반면에, 생성자 함수로 사용되었을 경우 해당 prototype 속성은 굉장히 중요한 의미를 갖는다. 그 의미를 계속해서 살펴보자.

Inheritance

자바스크립트에서는 프로토타입 방식을 통해 프로토타입 객체를 인스턴스에 상속한다.

function Location(country, latitude, longitude) {
  this.country = country;
  this.latitude = latitude;
  this.longitude = longitude;
}

Location.prototype.callMyLatitude = function() {
  console.log(`My latitude is ${this.latitude}`);
}

const southKorea = new Location("SouthKorea", 33, 124); 

southKorea.callMyLatitude();  //My latitude is 33


분명 southKorea 인스턴스에는 country, latitude, longitude라는 속성만 존재하지만, 해당 예시에서 southKorea.callMyLatitude()로 메소드를 실행하면 마치 자신이 callMyLatitude라는 메소드를 가지고 있듯, 이상없이 실행된다. 어떻게 가능 한 것일까?

각 인스턴스 객체는 각 생성자 함수의 prototype 속성(객체)을 상속받는다.

상속이란 (인스턴트에 존재하지 않더라도) 해당 객체의 prototype(생성자 함수의 prototype 객체)에 정의된 속성과 메소드에 접근하고 사용할 수 있는 권한을 부여 받았다는 것을 의미한다.

즉, southKorea 인스턴스는 callMyLatitude이라는 메소드를 가지고 있지 않지만, 생성자 함수의 프로토타입 객체를 상속받았기 때문에 그 프로토타입에 존재하는 callMyLatitude 메소드에 접근하여 사용할 수 있게 된 것이다.

인스턴스가 생성된 이후 프로토타입 객체에 새로운 속성이 추가될 경우에도, 이미 인스턴스는 프로토타입 객체에 대한 link를 가지고 있기 때문에 추가된 프로토타입 객체 속성에도 마찬가지로 접근이 가능하다.

const southKorea = new Location("SouthKorea", 33, 124); 

Location.prototype.callMyLongitude = function() {
  console.log(`My longitude is ${this.longitude}`);
}

southKorea.callMyLongitude();  //My latitude is 124

그런데 만약 southKorea 인스턴스 자체 같은 이름의 속성이나 메소드가 존재할 경우, 자신의 속성에만 접근하고 프로토타입의 속성은 접근하지 않는다.

Location.prototype.callMyLatitude = function() {
  console.log(`My latitude is ${this.latitude}`);
}

const southKorea = new Location("SouthKorea", 33, 124); 

southKorea.callMyLatitude() = function() {
  console.log("This is my own property");
}

southKorea.callMyLatitude();  // "This is my own property"

우리가 흔히 쓰는 배열의 메소드, 함수의 메소드, 객체의 메소드 또한 생성된 인스턴스가
각 생성자 함수의 프로토타입 객체(메소드를 정의하고 있는)를 상속받았기 떄문에 호출하고자하는 메소드에 접근할 수 있게되어 사용가능했던 것이다.

let a = [1, 2, 3]; // let a = new Array(1,2,3); 와 같다.

a.push(4); // a의 프로토타입에 정의되어있는 push 메소드에 접근

console.log(a); // [1, 2, 3, 4];

Prototype Chain

인스턴스에서 특정 속성에 접근하고 자하면, 먼저 자신 안에 속성이 정의되어있는지 탐색한다.
만약 발견하지 못하면 접근 행위는 인스턴스 객체의 프로토타입으로 이동하여 지속되고, 해당 프로토타입 객체 내에서 속성을 탐색한다.

만약 해당 프로토타입 객체 내에서 속성을 찾지 못한다면?

다시 한번 해당 프로토타입 객체의 프로토타입 객체에서 속성을 찾는다.
이렇게 객체는 해당 속성을 찾거나, 찾지 못하여 undefined이 될 때까지 탐색 과정이 이루어 지는데 이러한 객체들의 연쇄를 가리켜 프로토타입 체인(prototype chain)이라고 한다.

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

const chan = new Person("chan");

Person.prototype.callName = function() {
    console.log(`My name is ${this.name}`);
}

chan.callName(); // My name is chan
chan.hasOwnProperty("name"); // true
  1. chan이라는 인스턴스에는 hasOwnProperty라는 메소드가 없다.

  2. 프로토타입 체인을 통해 해당 프로토타입인 Person.prototype에 속성이 존재하는지 탐색하지만 존재하지 않는다.

  3. Person.prototype 또한 프로토타입을 가지기 때문에 프로토타입 체인을 통해 Perosn.prototype의 프로토타입을 탐색한다.(Perosn.prototype은 결국 객체이자 Object 생성자 함수의 인스턴스이다.)

  4. 최종적으로 Object.prototype에 존재하는 hasOwnProperty 메소드에 접근하여 사용 가능할 수 있게 된다.

상속? 행동 위임?

일반적으로 자바스크립트 프로토타입 개념에서
'자식 객체(인스턴스)가 부모 객체의 프로토타입을 상속받았다'라고 흔히 표현하지만,

엄격하게 따지자면 'A가 B에게 재산을 상속했다'라는 말은 'A는 재산이 더이상 없어졌고, B만 그 재산을 가지고 있다'라는 의미이다.
(물론 '상속'이라는 단어가 완전 틀리다는 것은 아니지만, 작동 방식을 완벽히 설명하기에는 무리가 따른다는 의미이다.)

자바스크립트에서의 프로토타입 체인의 작동되는 방식은 해당 인스턴스에서 속성에 접근했지만 없을 경우, 부모 객체의 prototype속성으로 타고 올라가 접근하는 방식이기 떄문에 '상속(Inheritance)'보다는 부모 객체에게 '행동 위임(behavior delegation)'라는 단어가 더 적합한 표현이다.

참고

profile
한가지를 알아도 제대로 알자

0개의 댓글