생성자 함수와 프로토타입

Kyung yup Lee·2021년 5월 25일
0

자바스크립트

목록 보기
5/12

생성자 함수와 프로토타입

자바스크립트에서 생성자 함수와 프로토타입은 떨어뜨려 생각할 수 없는 개념이다. 두 개념은 특별한 경우가 아니면, 짝을 지어 생성된다. 물론 항상 둘이 연결되진 않지만, 개념을 이해하는 데는 두 가지를 함께 이야기하는 것이 좋다.

생성자 함수

자바스크립트는 객체와 함수만 제대로 알면 거의 다 안 것이라고 했다. 그리고 객체를 생성하는 방법에는 여러 방법이 있었는데,

  • 객체 리터럴
  • Object 생성자 함수
  • 생성자 함수
  • Object.create
  • 클래스

였다.

객체를 생성하는 방법은 객체 리터럴을 이용하는 것이 가장 간단하고 편리하다. 실제로 객체를 생성하는 대부분의 방법은 객체 리터럴이다. 하지만 만약 객체를 여러개 만들어야 하는 경우가 있다면?
예를 들어, 화면에 수십개의 공기방울이 움직이는 기능을 구현해야 된다고 했을 때, 수십개의 공기방울에 대한 객체를 객체 리터럴로 하나 하나 만들 순 없을 것이다. 이럴 경우 생성자 함수를 사용한다. 객체에 대한 템플릿을 만들어 놓고, 인스턴스를 찍어내는 방법이 더 효율적이다.

생성자 함수의 인스턴스 생성 과정

function Bubble(radius) {
  this.radius = radius
  this.getDiameter = function(){
	return 2 * this.radius;
  }
  const circle1 = new Circle(5);
  const circle2 = new Circle(10);
  
}

위 처럼 코드가 있다고 하자.

1. 인스턴스 생성과 this 바인딩

생성자 함수는 암묵적으로 빈 객체를 만들고 해당 빈 객체로 생성자 함수에 선언되어 있는 this를 바인딩한다. 즉 this === 인스턴스 이다.

2. 인스턴스 초기화

해당 생성자 함수가 실행되면, this에 바인딩되어 있는 인스턴스를 받아온 인수 혹은 함수 내부에 기술된 값으로 초기화 한다.

3. 인스턴스 반환

위 함수에는 return this 가 생략되어있다. 생성자 함수는 this 바인딩을 하고 초기화를 끝낸 뒤에, 해당 인스턴스와 바인딩 된 this를 반환한다. 생성자 함수는 해당 리턴문을 생략해야 하고, 만약 명시적으로 객체를 반환한다면, this 대신 객체를 반환하게 된다. 원시값을 반환하면 무시하고, this 를 반환한다.

생성자 함수와 일반 함수의 차이

생성자 함수와 일반함수는 선언될 때는 근본적으로 차이가 없다. 호출을 할 때, new와 함께 호출되는가, 아닌가에 따라서 생성자 함수가 될 수도 있고, 일반함수가 될 수도 있다. 함수 내부에는 일반 객체가 갖는 메서드에 더해서 [[Call]] 과 [[Construct]] 라는 내부 메서드가 추가로 만들어져 있다. new와 함께 호출되면 Construct 메서드가 실행되고, 일반 함수로 호출되면 Call 메서드가 실행되면서 다르게 작동한다. 하지만 모든 함수가 Construct 메서드를 가지고 있는 것은 아니다. 함수는 호출될 수 있어야 하기 때문에, Call 메서드는 반드시 가지고 있지만, Construct 메서드는 가지고 있지 않을 수 있다. 이를 non-constructor 라고 부르는데, 자바스크립트의 축약형 메서드화살표 함수 가 non-constructor 이다.
함수를 호출하는 입장에서는 함수 내부 코드를 까보지 않고서는 이 함수가 생성자 함수인지 일반 함수인지 구분하기가 힘들다.

프로토타입

생성자 함수를 이야기 하면서 빠질 수 없는 것이 프로토타입이다. 프로토타입은 자바스크립트에서 객체지향, 특히 상속을 구현하기 위해서 사용되는 개념이다. ES6에서 클래스가 도입되었지만, 자바스크립트의 클래스는 함수이며 프로토타입을 기반으로 동작한다.

자바스크립트는 프로토타입을 통해 상속을 구현하고 중복을 제거한다. 생성자 함수를 사용해서 100개의 객체를 만들어낸다고 했을 때, 타 객체지향 프로그래밍 언어는 클래스 상속을 통해 중복되는 메서드를 제거한다. 자바스크립트는 이것을 프로토타입을 통해 구현한다. 생성자 함수를 통해 프로퍼티를 가지고 있는 인스턴스를 생성하고, 이 인스턴스의 프로토타입에 메서드를 할당하는 것을 통해 상속을 구현한다.

생성자 함수와 프로토타입의 관계

생성자 함수와 프로토타입 객체는 짝궁 관계이다. 생성자 함수는 기본적으로 prototype 이라는 프로퍼티를 가지고 있다. 이 prototype 프로퍼티는 함께 만들어지는 프로토타입을 가리키며, 마찬가지로 이 프로토타입 또한 constructor라는 프로퍼티를 가져서 생성자 함수를 가리키고 있다. 생성자 함수, 프로토타입, 인스턴스는 삼각형의 관계를 이루고 있다. 생성자 함수는 인스턴스를 생성하고 이 인스턴스는 프로토타입을 상속받고 있다. 그리고 생성자 함수와 프로토타입은 서로를 참조할 수 있다.

프로토타입의 생성 시점

프로토 타입은 생성자 함수가 생성되는 시점에 함께 생성된다. 왜냐하면 생성자 함수와 프로토타입은 항상 쌍으로 존재해야 하기 때문이다. 즉 non-constructor 함수는 프로토타입이 생성되지 않는다.

빌트인 생성자 함수의 프로토타입 생성 시점

Object, String, Number, Funciton, Array, Date 등의 생성자 함수를 빌트인 생성자 함수라고 한다. 이 빌트인 생성자 함수 또한 생성자 함수가 생성됨과 동시에 프로토타입이 만들어진다. 하지만 사용자 정의 생성자 함수와 다른 것은 이 빌트인 생성자 함수는 전역 객체(window)가 만들어지는 시점에 만들어진다는 것이다.

여기서 중요한 것이 Object 생성자 함수이다. 이 Object 생성자 함수가 생성되면서 Object.prototype 즉 Object 생성자 함수와 짝을 이루는 프로토타입이 생성된다. 자바스크립트의 모든 데이터는 원시 타입을 제외하고 모두 객체라고 했다. 그렇기 때문에 원시타입을 제외한 모든 데이터는 이 Object.prototype 을 상속받게 된다. 자바스크립트의 가장 최상단에 존재하는 프로토타입은 이 프로토타입이다.

이를 통해 생성자 함수를 호출해 인스턴스를 만들거나, 객체 리터럴을 통해 객체를 만들게 되면, 미리 만들어져있는 생성자 함수와 프로토타입에 바인딩 되게 된다.

객체 생성 방식과 프로토타입 결정 방식

객체를 생성하는 방법은 여러가지가 있는데 대표적으로 객체 리터럴, Object 생성자 함수, 생성자 함수 세 가지 방법이 있다. 그리고 이 방식 모두 프로토타입이 연결되는 방식이 다르다.

바로 위에 서술했듯이 빌트인 객체는 프로그램이 시작되자마자 해당 생성자 함수가 만들어지고, 프로토타입도 함께 생성되게 된다.

객체 리터럴

개체 리터럴의 경우 생성자 함수가 따로 없다. 즉 템플릿을 통해 생성하는 것이 아니라 리터럴로 값을 만들어내는 방법이다. 객체 리터럴에 의해 평가되어 생성된 객체는 미리 만들어져있는 Object.prototype 을 상속받게 된다. 이는 객체 리터럴로 만들어진 객체가 Object 생성자 함수로 만들어졌다는 말과는 다르다. Object.prototype 이 Object 생성자 함수와 짝을 이루는 것은 맞지만 이것이 꼭 객체 리터럴이 Object 생성자 함수로 만들어졌다는 의미는 아니다. 상속을 위해 이미 생성된 Object 프로토타입에 연결을 한다고만 이해하면 된다.

Object 생성자 함수

Object 생성자 함수로 만들어진 객체는 생성자 함수와 마찬가지로 Object 생성자 함수를 new 연산자와 함께 인스턴스로 만들어진 객체이다. 그러므로 인스턴스가 생성됨과 동시에 Object.prototype을 상속받게 된다. 객체 리터럴과의 차이는 new 연산자를 통해 객체를 만들어 냈는가 아닌가이다.

생성자 함수

생성자 함수를 통해 만들어진 객체는 위에서도 서술했듯이, 생성자 함수를 new 연산자로 호출하면서 만들어진다.

프로토타입 체인과 오버라이딩

오버라이딩

오버라이딩은 상속받은 메서드를 재정의해서 사용하는 것을 말한다. 오버라이딩은 메서드 이름이 같아야 한다. 자바에서는 인터페이스를 구현하거나, 추상메서드를 메꾸기 위해 많이 사용한다. 자바스크립트도 오버라이딩을 비슷하게 구현한다. 하지만 여기에는 shadowing 개념이 들어간다. 상속받은 메서드를 재정의한다기 보다, 메서드 이름이 같기 때문에, 하위 프로토타입 체인에서 먼저 메서드를 정의하면 그 부모의 메서드는 더 이상 참조하지 않는다.

프로토타입 체인

프로토타입 체인은 프로토타입의 상속관계를 나타낸다. 사용자 정의 생성자 함수와 이를 통해 만들어진 프로토타입이 있을 때, 이들이 최상위 프로토타입이 아니다. 프로그램이 시작되자마자 만들어진 빌트인 함수 생성자 함수가 있고, 이와 함께 만들어진 프로토타입이 있다고 했다. 특히 자바스크립트의 모든 객체는 Object.prototype을 최상위 객체로 갖는다고 했다.
이 사이에 사용자 정의 생성자 함수로 만들어진 인스턴스까지의 모든 상속 관계를 프로토타입 체인이라고 한다. 이 과정에서 오버라이딩 및 shadowing이라는 현상이 발생하게 된다. 이 프로토타입 체인이 중요한 이유는 특정 객체에서 프로퍼티나 메서드를 찾으려고 할 때, 만약 가장 최하단 객체에서 이 요소가 없다면 프로토타입 체인을 따라 올라가면서 탐색을 하기 때문이다. 때문에, 예상치 못한 결과를 막기 위해서는 이 프로토타입 체인과, 부모 프로토타입들에 대해서 알고 있어야 한다.
대표적인 예로, name 프로퍼티 키를 찾으려고 하는데, 인스턴스에 없을 경우, window 객체에서 이를 호출하게 된다. 하지만 개발자는 최하단 인스턴스 객체에서 없으면 값이 없는것을 기대했을 것이다

profile
성장하는 개발자

0개의 댓글