프로토타입
자바스크립트의 거의 모든 것은 객체다 -누군가 했던 말인 거 같은데
자바스크립트의 거의 모든 것은 객체다 -누군가 했던 말인 거 같은데
객체 지향 프로그래밍
프로그래밍을 공부하다 보면 한 번쯤 들어봤을 것이다. 사람들이 설명할 때 항상 절차지향 프로그래밍을 같이 설명해서 이 둘이 서로 대척점에 놓인 것이라고 생각하는 경우도 있는데 사실은 그렇지 않다. 초기 프로그래밍 방법은 명령형 코드를 절차 지향적으로 작성했다. 이후 객체 지향 프로그래밍이라는 프로그래밍 스타일이 등장하면서 프로그램 패러다임이 전환된 것이다.
절차 지향 프로그래밍
샌드위치를 만든다고 하자. 이를 코드로 작성할 때 아래와 같이 작성하는 것이 절차 지향으로 프로그래밍을 하는 것이다.
빵을 굽는다
상추를 씻는다
토마토를 썬다
햄을 익힌다
빵 위에 상추를 올린다
그 위에 토마토를 올린다
그 위에 햄을 올린다
그 위에 소스를 뿌린다
빵을 덮는다
위와 같은 방식으로 코드를 짜는 것이 절차 지향 프로그래밍 방식이다. 어떤 결과를 위해 그 과정을 하나 하나 코드로 작성하는 것이라고 생각하면 된다. 이렇게 되면 절차만 알고 있다면 코드를 작성하는 것이 쉽게 된다. 하지만, 코드가 길어질 수 있고, 위 코드를 재사용하기 어려울 수 있다는 단점이 있다. 또한 코드의 목표를 정확히 알기 어렵다.
이런 방식을 명령형 프로그래밍이라고도 한다. 명령형 프로그래밍과 반대의 의미 정도로 할 수 있는 것은 선언형 프로그래밍이다. 선언형 프로그래밍은 내부 로직을 명시하는 것이 아니라 목표를 명시하여 구체적인 로직을 추상화할 수 있다.
객체 지향 프로그래밍
객체 지향 프로그래밍은 공장을 만드는 방법이라고 생각해야 한다. 실제 우리는 살아가면서 객체 지향의 관점으로 여러 가지를 볼 수 있다.
예를 들면, 차의 종류를 들은 다양하지만, 공통으로 가지고 있는 특징들이 있다. 차는 연료를 채우거나, 운전을 하거나, 바퀴가 있다거나 하는 특징들을 모두 가지고 있다. 하지만, 차의 색이나, 바퀴의 갯수, 운전석의 위치 등은 다를 수 있다. 그렇다고 해도 그 모든 것은 차라고 할 수 있다. 이런 관점으로 프로그래밍을 하는 것이 객체 지향 프로그래밍이다.
class Car{
constructor(color, wheels,name){
this.name = name;
this.color = color;
this.wheels = wheels;
}
drive(){
console.log(`${this.name} is driving`);
}
refuel(){
console.log('refuel');
}
}
class Tico extends Car{
constructor(color, name){
super(color,4,name);
this.made = 'Daewoo'
}
}
const tico1 = new Tico('red','my tico');
const tico2 = new Tico('blue','old tico');
자바스크립트에서의 객체 지향
자바스크립트는 객체 지향 프로그래밍이 가능하지만 클래스 기반 객체 지향 언어가 아니다. 바로 프로토타입을 이용한 객체 지향을 구현했다. 객체 지향의 핵심이라고 할 수 있는 상속을 프로토타입을 이용해서 구현한 것이다. 그렇기 때문에 실제 클래스 기반 언어에서 제공되는 기능들이 자바스크립트에서는 제공되지 않는 경우도 있다. 하지만 그것들과 비슷하게 구현이 가능하기 때문에 객체 지향 프로그래밍이 가능하다.
기존에 자바스크립트는 클래스 문법이 없었다. 그렇기 때문에 생성자 함수를 이용해서 클래스 역할을 구현했다.
function Car(color, wheels, name){
this.name = name;
this.color = color;
this.wheels = wheels;
}
Car.prototype.drive = function(){
console.log(`${this.name} is driving`);
};
Car.prototype.refuel = function(){
console.log('refuel');
}
위와 같은 방식으로 클래스와 같이 구현을 했다. 만약 메소드를 상속받고 싶다면 Object.create를 이용해서 프로토타입을 이어주고, prototype 의 consturctor를 바꿔주는 방식으로 구현했다.
function Tico(color,wheels,name){
// ...
}
Tico.prototype = Object.create(Car.prototype);
Tico.prototype.constructor = Tico;
이후 등장한 클래스 문법은 위의 기능을 좀 더 간편하고 실제 클래스 기반 언어와 비슷하게 만들어 놓은 것이다. 상속의 기능도 그렇고, 클래스 확장도 그러하다. 하지만 생성자 함수는 그런 기능을 제공하지 않아 위와 같은 방식으로 구현해야 하는 단점이 있다.
프로토타입
생성자 함수가 호출되면 프로토타입을 생성하게 된다. 프로토타입은 자바스크립트가 객체 지향 언어로 동작하는 데 필수적으로 있어야 하는 것이다. 생성자 함수가 만들어 내는 프로토타입은 객체이다. 객체는 프로퍼티와 메소드를 가질 수 있기 때문에 위의 예시에서 Car.prototype.drive와 같은 식으로 메소드를 만들 수 있는 것이다.
프로토타입은 상속의 개념을 위해 필요한 것이기 때문에 같은 생성자 함수가 만든 인스턴스들은 프로토타입에 있는 프로퍼티나 메소드는 인스턴스가 가지고 있던 것처럼 사용할 수 있다.
프로토타입이 어떤 형태인지 보고 싶다면 console.dir로 확인할 수 있다.

위의 사진은 Tico 클래스를 이용해 인스턴스를 만든 것이다. 자신의 상위 클래스의 프로토타입은 [[prototype]]이라는 내부 슬롯에 있는 것을 확인할 수 있다. 위의 경우 Car에서 Tico가 확장된 경우이기 때문에 Car의 프로토타입이 써있는 것처럼 보이지만, 내부를 살피면 이는 Tico의 프로토타입이라는 것을 알 수 있다.
그 이유는 바로 constructor라는 프로퍼티 때문이다. 생성자 함수가 프로토타입을 만들게 되면, 먼저 constructor프로퍼티에 자신을 바인딩하게 된다. 그 후 메소드들을 추가하게 되는 것이다.

다시 아까 사진에서 프로토타입 내부 슬롯을 열어보면 constructor프로퍼티에 class Tico가 있는 것을 볼 수 있다. 즉 첫번째 프로토타입은 Tico가 생성한 프로토타입인 것이다.
여기서 첫번째 프로토타입이라고 했다. 그 뜻은 두번째도 있다는 것이다. 실제로 프로토타입의 내부슬롯에 또 프로토타입 내부슬롯이 있다. 이 내부 슬롯을 펼쳐보면 상위 클래스의 프로토타입이 있다는 것을 볼 수 있다.
.__proto__
그렇다면 인스턴스의 클래스가 생성한 프로토타입을 확인하거나 접근하고 싶을 때는 어떻게 해야할까? 바로 __proto__를 이용하는 것이다.
__proto__는 접근자 프로퍼티다. 접근자 프로퍼티는 객체의 프로퍼티처럼 접근할 수 있지만, 실제로 값을 가지고 있는 것은 아니고 다른 프로퍼티의 값을 가지고 오는 프로퍼티를 뜻한다. 여기서 __proto__는 [[prototype]]내부슬롯의 값을 가지고 오기 때문에 접근자 프로퍼티라고 하는 것이다.
__proto__를 이용하면 상위 클래스가 만든 프로토타입 객체에 접근할 수 있다. 이를 확인하는 방법은 아래와 같다.
tico1.__proto__ === Tico.prototype; // true
// tico1의 상위 클래스의 프로토타입 객체는 Tico가 생성한 프로토타입을 의미한다.
프로토타입 체인
가끔 객체를 보면, 자신에게 없는 메소드나 프로퍼티에 접근하는 경우가 있다. 어떻게 가능한 것일까? 바로 프로토타입 덕분이다. 만약 자신에게 없는 메소드가 자신의 상위 클래스 프로토타입에 있다면, 그 마치 자신이 가지고 있던 것처럼 사용할 수 있다.

위의 tico1인스턴스는 자신을 생성한 클래스 Tico에 없는 메소드인 drive를 호출하고 있다. 그 이유는 Tico가 Car의 프로토타입을 상속받았기 때문이다. 즉, Tico로 생성한 인스턴스들은 Car의 프로토타입에 있는 메소드도 사용할 수 있다는 것이다. 그렇다면 toString메소드는 왜 사용이 가능할까? 모든 프로토타입 객체의 최상위는 Object.prototype이다. 그렇기 때문에 Object.prototype에 있는 메소드도 사용할 수 있다는 것이다.
이렇게 메소드나 프로퍼티를 찾아갈 때 프로토타입을 타고 올라가는 구조를 보고 프로토타입 체인이라고 한다. 마치 스코프 체인과 같다. 식별자를 찾을 때는 스코프체인을 이용하고, 프로퍼티나 메소드를 찾을 때는 프로토타입 체인을 이용하게 되는 것이다.
프로토타입 체인은 두개가 이어진다고 생각해야 한다. 하나는 클래스 간의 프로토타입, 다른 하나는 인스턴스의 프로토타입이다. 보통 상속받은 메소드를 확인할 때는 인스턴스의 프로토타입 체인을 확인한다는 것이다. 그렇다면 클래스의 프로토타입 체인은 무엇일까?
사실 클래스는 함수이다. 즉, 클래스는 함수의 프로토타입을 통해 프로토타입 체인이 이어진 것이다. 클래스의 __proto__로 올라가다보면 Function.prototype을 만날 수 있다. 여기서 중요한 것은 Function.prototype은 Function이라는 생성자 함수가 생성한 프로토타입 객체이기 때문에 Function.prototype.__proto__는 Object.prototype을 가리키게 된다.
웃긴 것은 Function.__proto__는 Function.prototype이라는 것이다. 즉 Function.__proto__.__proto__는 Object.prototype에서 온다. 이렇게 보면 모든 것의 시작은 Object.prototype이라고 할 수 있다.