본문 바로가기
JAVASCRIPT

생성자 함수와 Prototype

by escaper101 2020. 11. 17.

Prototype을 설명하기 전 생성자 함수(constructor function)를 이해하는 게 좋다. 

 

생성자 함수는 다음과 같은 특징이 있다. 

1. 일반 함수와 같지만 생성자 함수를 호출할 때 new 예약어를 사용한다.

- new 예약어를 사용하여 생성자 함수를 호출하는 순간 다음과 같은 일이 발생한다. 

  • 새로운 빈 객체가 생성됨.
  • this 키워드가 만들어진 빈 객체를 가리킴.
  • prototype 프로퍼티가 만들어짐.
  • 위에서 만들어진 객체가 리턴됨. 

2. 생성자 함수명의 첫글자는 대문자로 한다. 

3. 생성자 함수는 화살표 함수가 아닌 일반 함수로 선언해야 한다. 

 

위 내용을 주의하여 생성자 함수를 만들어 보자. 

const Book = function(name, publicationYear) {
  console.log(this)
}

// Book {}
new Book('Pride and Prejudice', 1813);

위 코드에서 Book이라는 생성자 함수를 만들었다. 앞서 말한 대로 new 키워드로 Book을 호출하면 빈 객체가 만들어지고 this 키워드는 이 객체를 가리킨다. this 키워드를 콘솔에 출력해보면 빈 Book 객체를 출력하는 것을 확인할 수 있다. 이제 만들어진 Book 생성자 함수를 좀 더 다듬어 사용해보자. 

const Book = function(name, publicationYear) {
  // Instance Properties 
  this.name = name;
  this.publicationYear = publicationYear;

  this.isClassic = function() {
    console.log(2020 - this.publicationYear >= 100)
  }
}

/*
Book {
  name: 'Pride and Prejudice',
  publicationYear: 1813,
  isClassic: [Function]
}
*/
const literature = new Book('Pride and Prejudice', 1813);

 

Book 생성자 함수는 name, publicationYear 변수와 isClassic 메서드를 가진다. 따라서 Book 생성자 함수를 호출할 때 만들어진 빈 객체의 name과 publicationYear 프로퍼티가 각 매개변수로 전달한 값을 가진다. 11번 줄에서 이 객체를 이제 literature 객체에 담았다. 

 

JavaScript가 아닌 다른 타입 언어를 사용해 본적이 있다면 객체를 생성하는 익숙한 패턴일 것이다. JavaScript는 클래스가 없지만 생성자 함수는 JavaScript로 OOP 프로그래밍을 가능하게 한다. 여기서 생성자 함수는 마치 클래스처럼 작동하고 new 예약어를 통해 리턴된 객체는 생성자 함수의 인스턴스, 즉 객체와 같다. 따라서 생성자 함수에 정의한 프로퍼티들을 인스턴스 프로퍼티라고 부른다. 

 

위 코드를 다시 살펴보면 Book 생성자 함수는 isClassic 메서드를 가지고 있다. 생성자 함수에 바로 메서드를 만드는 것은 좋은 방법이 아니다. 왜냐하면 생성자 함수롤 통해 만들어지는 모든 객체에 이 메서드가 복사되어 이 생성자 함수로 만들어지는 객체의 수가 많아질 경우 성능 이슈가 발생할 수 있기 때문이다. 그렇다면 어떤 방법으로 생성자 함수를 통해 만들어진 객체에 메서드를 공유할까? 바로 prototype을 통해서 할 수 있다. 

 

Prototype의 개념을 다음과 같이 설명할 수 있다. 

본 포스팅 서두에서 생성자 함수를 설명할 때, new 예약어로 생성자 함수를 호출하면 prototype 프로퍼티가 만들어진다고 했다. 이때, 어떤 생성자 함수로 만들어진 모든 객체들은 생성자 함수의 prototype 프로퍼티에 정의된 모든 메서드와 변수들에 접근할 수 있다. 

 

const Book = function(name, publicationYear) {
  // Instance Properties 
  this.name = name;
  this.publicationYear = publicationYear;
}

Book.prototype.isClassic = function() {
  console.log(2020 - this.publicationYear >= 100)
}

const literature = new Book('Pride and Prejudice', 1813);
// true
literature.isClassic();

 

Book 생성자 함수 안의 isClassic 메서드를 삭제하고 prototype에 동일한 메서드를 추가했다. 그 결과 Book 생성자 함수로 만든 객체 literature에서 isClassic 메서드를 사용할 수 있다. 이렇게 isClassic 메서드를 Book 생성자 함수의 prototype에 한번만 만들어 놓고 Book 을 통해 만든 모든 객체에서 isClassic 메서드를 재사용할 수 있다. 

 

JavaScript의 모든 객체는 __proto__라는 이름의 prototype을 가진다. 이는 위 예시의 7번 줄 Book.prototype과 다르다.

prototype 프로퍼티는 생성자 함수만이 가지는 특수한 프로퍼티이고 이 프로퍼티는 모든 JavaScript 객체가 가진 __proto__ 프로퍼티와 다르다. (두 개를 헷갈리지 않길 바란다.) 이렇게 모든 객체가 가지는 __proto__ 프로퍼티를 통해 Prototype 상속(prototype inheritance)이 가능해진다. 

 

따라서 literature 객에 역시 __proto__ 프로퍼티를 가진다. 이때, literature의 __proto__ 프로퍼티는 literature의 생성자 함수인 Book의 prototype 프로퍼티를 가리킨다. (14줄 ~ 16줄 주목)

const Book = function(name, publicationYear) {
  // Instance Properties 
  this.name = name;
  this.publicationYear = publicationYear;
  console.log(this)
}

Book.prototype.isClassic = function() {
  console.log(2020 - this.publicationYear >= 100)
}

const literature = new Book('Pride and Prejudice', 1813);

const isSame = literature.__proto__ === Book.prototype;
// true
console.log(isSame);

Book의 prototype 프로퍼티 역시 객체이므로 이 객체 역시 __proto__ 프로퍼티를 가진다. 그렇다면 Book의 prototype 프로퍼티의 __proto__는 무엇을 가리킬까? 바로 모든 객체의 최상위 생성자 함수인 Object의 prototype을 가리킨다. 아래 코드를 보자. 

const isSame = literature.__proto__ === Book.prototype;
console.log(isSame); // true
const isAlsoSame = Book.prototype.__proto === Object.prototype;
console.log(isSame); // true

다음 그림을 보면 JavaScript의 프로토타입 상속을 이해할 수 있을 것이다. literature.__proto__ => Book.prototype => Book.prototype.__proto___ => Object.prototype 으로 연결되어 있다. 

프로토타입 상속

우리는 바로 이 프로퍼티 상속을 바탕으로 프로퍼티 체인을 활용할 수 있다. 앞서 살펴본 예제에서 우리는 literature 객체에서 isClassic 메서드를 사용할 수 있었다. isClassic 메서드는 literature 객체에 직접 존재하는 메서드는 아니지만 literature 객체의 생성자 함수인 Book의 prototype 프로퍼티에 저장했기 때문이다. 이렇게 JavaScript는 일단 객체 자체를 스캔하고 원하는 메서드가 없는 경우 그 객체의 __proto__를 통해 생성자 함수의 prototype에 접근하여 메서드를 찾는다. 이 작업은 메서드를 찾을때까지 반복되거나 모든 객체의 최상위 생성자 함수인 Object의 __proto__에 다다랐을 때 비로소 찾기를 멈춘다. 이 과정을 가능하게 하는 것이 프로토타입 체인(Prototype Chain)이다. Object의 __proto__는 null이기 때문에 원하는 메서드를 프로토타입 체인에서 찾을 수 없을 때 null을 리턴한다. 

 

 

프로토타입 체인 

 

'JAVASCRIPT' 카테고리의 다른 글

비동기 프로그래밍  (0) 2020.11.28
Runtime 과 Call Stack  (0) 2020.11.28
Closure의 이해  (0) 2020.11.17
this 키워드  (0) 2020.11.16
Hoisting과 Temporal Dead Zone (TDZ)  (0) 2020.11.16