객체지향 프로그래밍과 프로토타입
자바스크립트는 public,private,protected 등의 키워드가 없어서 객체지향 프로그래밍이 아니라는 오해를 받지만 자바스크립트는 클래스 기반 객체지향 프로그래밍 언어보다 효율적이며 더 강력한 프로토타입 기반의 객체지향 프로그래밍 언어이다.
객체지향 프로그래밍
객체지향 프로그래밍이란, 특성을 나타내는 속성들의 집합을 통해 실체를 나타내어 구별하거나 인식하는 개념을 프로그래밍에 접목시킨 프로그래밍 패러다임이다. 여기서 프로그램에 필요한 속성만을 간추려 추상화 한것이다.
1 | 고양이 = { |
프로토타입과 상속
자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거한다.
1 | // 불필요한 메서드 중복 소유 |
위 예시는 Circle 생성자 함수가 인스턴스를 생성할 때마다 동일한 동작을 하는 getArea 메서드를 중복 생성하는데, 이 경우 모든 인스턴스가 메서드를 중복 소유하여 메모리 낭비를 야기시킬 수 있다.
1 | // 프로토타입 상속을 기반으로 불필요한 중복 제거 |
프로토타입 객체
프로토타입 객체(줄여서 프로토타입)은 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체의 공유 프로퍼티(메서드 포함)를 제공한다.
- 객체간 상속을 구현하기 위해 사용
- 프로토타입을 상속받은 하위 객체는 상위 객체의 프로퍼티를 자유롭게 사용
앞으로 나올 개념은 그림이 없으면 이해하기 어렵다. 이 블로그는 나의 생각을 정리해 둔 것이므로 이해를 돕기 위해서 모던 자바스크립트 Deep Dive 책을 참고하도록하자. 혹은 Poiema Web 사이트를 참고하자.
객체 생성 방식에 따라 객체가 생성될 때, 프로토타입이 결정되고 [[Prototype]] 내부 슬롯의 값으로 저장된다.
함수객체의 prototype 프로퍼티
함수 객체만이 갖는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
함수 정의 방식에 따라 일반함수 정의할 경우 constructor이고 ES6에서 정한 메서드 축약표현, 화살표 함수로 함수 정의할 경우 non-constructor이다.
non-constructor인 화살표 함수, 메서드 축약표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않는다. ⇒ 프로토타입도 생성하지 않는다.
프로토타입 객체(프로토타입), 생성자 함수의 prototype 프로퍼티, 프로토타입의 constructor 프로퍼티, 생성자 함수가 생성한 객체(인스턴스)의 proto 등 헷갈리는 용어들이 많이 나오니 혼동하지 않도록 한다.
프로토타입의 constructor 프로퍼티와 생성자 함수
모든 프로토타입은 constructor 프로퍼티를 갖는다. constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.
프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재하며 생성자 함수가 생성되는 시점과 더불어 프로토타입도 생성된다.
프로토타입의 생성 시점
생성자 함수가 생성되는 시점과 더불어 프로토타입도 생성된다.
사용자 정의 생성자 함수는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
1 | // 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성 |
함수 선언문은 함수 호이스팅이 일어나 런타임 이전에 자바스크립트 엔진에 의해 실행된다.
이 때 함수 객체가 생성되고 더불어 생성된 프로토타입은 Person 생성자 함수의 prototype의 프로퍼티에 바인딩된다.
이 때 생성된 프로토타입은 오직 constructor 프로퍼티만 갖는 객체이다.
생성된 프로토타입의 프로토타입은 항상 Object.prototype이다.
반면, 빌트인 생성자 함수의 경우 전역 객체가 생성되는 시점에 생성된다.
즉, 객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화 되어 존재한다. 이 후 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다. 그 결과 생성된 객체는 프로토타입의 상속을 받는다.
객체 생성 방식과 프로토타입의 결정
객체는 다양한 방식으로 생성되어 세부적인 객체 생성 방식의 차이는 있지만 모두 추상연산 OrdinaryObjectCreate에 의해 생성된다는 공통점이 있다.
추상연산 OrdinaryObjectCreate는 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달받는다.
즉, 프로토타입은 추상연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정된다. 인수는 객체가 생성되는 시점에 객체 생성 방식에 의해 결정된다.
- 객체 리터럴로 생성된 객체의 프로토타입
1 | Object.prototype; |
- Object 생성자 함수로 생성된 객체의 프로토타입
1 | Object.prototype; |
- 사용자 정의 생성자 함수로 생성된 객체의 프로토타입
1 | 생성자함수.prototype; |
생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체이다. 이 프로토타입은 constructor 프로퍼티만 가지고 있다.
프로토타입 체인
자바스크립트는 객체의 프로퍼티에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 한다.
1 | function Person(name) { |
Person 생성자 함수에 의해 생성된 me 인스턴스는 Object.prototype의 메서드인 hasOwnProperty 메서드를 호출할 수 있다. 이것은 Person.prototype뿐만 아니라 Object.prototype도 상속 받았다는 것을 알 수 있다.
단, me 객체의 프로토타입은 Person.prototype이다.
그리고 Person.prototype의 프로토타입은 Object.prototype이다.
프로토타입 최상위에 있는 객체는 언제나 Object.prototype(프로토타입 체인의 종점)이다. 그러므로 Object.prototype의 [[Prototype]] 내부 슬롯은 null 이다.
식별자와 프로퍼티 차이
식별자는 함수의 중첩관계로 이루어진 스코프의 계층적 구조로 이뤄진 스코프 체인에서 검색하여 찾는다.
반면에 객체의 프로퍼티는 해당 객체의 프로퍼티가 없다면 프로토타입 체인을 따라 [[Prototype]] 내부 슬롯에 바인딩된 프로토타입으로 이동하면서 프로퍼티를 검색한다.
스코프 체인과 프로토타입 체인은 별도로 동작하지 않고 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용된다.
소감
프로토타입에 대해 설명하기가 무척 어렵고 난해했는데 그림을 보며 공부하니 이해가 수월하였다. TIL을 정리하면서 저작권때문에 그림을 첨부할 순 없었지만 머릿속으로 다시 그려보면서 정리하니 기억에 오래 남을 것 같다.
이제 러버덕을 하면서 말로 설명해보는 시간을 가져야겠다.