오전에는 객체지향 프로그래밍이라는 것을 나만의 방식으로 분석하고 이해하는 시간을 가지고 포스팅했다. 누구나 본인이 처음 마주한 것을 이해하려 할때는 자신만의 방식으로 이해하는 것이 가장 효율적이라는 말에 찬성한다. 이전까지 머릿속에서 맴돌기만 하던 것이 조금은 쉽게 이해될 수 있는 시간이었다. 얼추 이해한 것 같을 때, 글로 정리하는 것이 이해에 얼마나 큰 도움이 되는지 알게 해주는 시간이었다. 앞으로 TIL을 작성할 때에는, 개념을 적는 것보다 그 개념을 통해서 알게된 것이나 더 삽질해서 알게된 내용들을 적는 것이 훨씬 더 크게 도움이 될 거라는 생각이 들었다.
지금부터 적어나가고자 하는 내용은 객체지향 프로그래밍의 방법론을 닮은 JavaScript의 Prototype기반의 OOP에 대해서 정리하려고 하는 글이다. 글을 쓰는 와중에 실제 내용과 틀린 부분이 많을지도 모르지만, 하나하나 배워나가는 과정이기에 이해한대로 적다보면 언젠가 더 익숙해졌을 때에 이 글을 수정하게 되는 날이 오리라 믿어 의심치 않는다.
나는 항상 쓰기에 앞서, 용어를 미리 이해하고 넘어가는 것을 앞으로 주요한 방법으로 사용하는 것이 좋을 것 같다는 생각이 들어서 글을 쓸 때에는 항상 영어단어의 의미를 먼저 파악하고 항상 글을 시작하는 것으로 오늘부터 결정하겠다.
참고로 영어 단어 뜻을 몰라서 작성하는 것은 아니다. 그냥 명시적으로 해두는 것이 나중에 이해하는 것에 있어서도 빠르게 의미로 연결되기 때문이다.
Prototype (명사)
원형(原型), 시험제작원형
말그대로 프로토타입은 원형(原型)을 뜻한다. 어떤 상품을 제작할 때에 가장 맨처음 만든 테스터 제품을 프로토타입이라고 부르는 이유는 바로 그런 것이다. 뭔가를 만들기 위한 틀이 되는 대상이며, 가장 초기에 만들어지는 무언가다. 기본적으로 JavaScript는 이러한 Prototype을 기반으로 이루어진 OOP 체계를 가지고 있다. 쉽게 말하면 인간의 최상단의 Prototype은 아마도 아미노산일 것이다. 지구가 처음 만들어지고 생명이 시작된 근원은 아미노산이었으니까.
간단하게 구글링을 통해 가져온 가계도 다이어그램이다. 이 그림이 아무래도, 프로토타입의 개념을 이해하는데에 크게 도움이 될거라는 생각에서 가져왔다. 위 이미지를 보면 부모 사이의 자녀는 밑으로 계속해서 뻗어나온다. 밑의 자식의 부모는 바로 위에 있고, 계속해서 올라가면 증조할머니, 증조할아버지까지 이어진다. 이 그림에서 가장 위에 있는 것이, 나 자신의 최고 조상이 된다.
이걸 JavaScript에서 보면 위처럼 표현할 수 있을텐데, 총 3번의 상속을 받은 클래스가 있다고 치자. 이 클래스는 이름은 알 수 없지만, 그 원형인 prototype을 3번 올라가보면 최고위의 prototype을 만날 수 있게 된다. 사람은 가계도에서 내려오면서 조상의 특징이나 생김새를 닮아오게 되는데 Prototype 역시 그런 특징들을 닮는 것과 비슷한 상속을 한다.
위의 내용을 코드를 통해 확인해보자.
const developer = { name: 'kim', age : 29 } console.log(developer.hasOwnProperty('name')); // true
굳이 class를 만들어서, instance를 생성하지 않더라도 Javascript에서는 변수에 할당함으로서 객체를 생성할 수 있다. 생성한 developer 객체에는 어디에도 hasOwnProperty()라는 메소드를 발견할 수 없지만, 콘솔결과를 받을 수 있다.
콘솔창에 객체를 찍어보면 알 수 있는데, __proto__
라는 속성을 가지고 있다. 이 __proto__
를 접근하게 되면 내부적으로 Object.getPrototypeOf
가 호출되며 프로토타입 객체를 반환한다. 위의 경우에는 따로 상속받은 Prototype이 없기 때문에 바로 위에 있는 Object() 원형 프로토타입이 반환된다.
__proto__
속성은 자신의 부모객체인 Object.prototype을 가리키고 있다.
console.log(developer.__proto__ === Object.prototype); // true
객체(class와 fucntion도 객체에 포함된다.)를 생성할 때, prototype이 결정된다. 이후에 만약 developer.__proto__ = Person
을 입력하게되면, developer의 prototype은 Person을 가리키는 것으로 변경되며, 부모 객체를 바꿀 수 있다는 것을 의미한다. 이를 이용해서 동적으로 상속받을 수 있는 객체 상속"처럼" 사용할 수 있다.
모든 객체는 자기 자신의 프로토타입 객체를 가리키는 [[Prototype]]인터널 슬롯(internal slot)을 가지고 있다. 함수도 객체이므로 인터널 슬롯을 가지고 있다. 근데 함수 객체는 일반 객체와 달리 prototype이라는 속성도 따로 가지고 있다.
prototype !== [[prototype]]
둘다 프로토타입 객체를 가리키지만 다른 것이다.
__proto__
Function.prototype
을 가리킨다.프로토타입의 객체는 constructor를 갖는다. 본인을 생성한 객체를 가리킨다. 원본 프로토타입(함수 객체명)을 가리킨다고 보면된다.
function Dev(name) { this.name = name; }
다음과 같이 Person 생성자 함수를 만들면, Person을 통해 만들어지는 인스턴스의 constructor는 Person 함수가 되고, Person 자신의 프로토타입의 생성자 함수도 Person함수가 된다. 하지만 Person생성자함수는 함수이므로 자신의 constructor는 Function이 된다. 여기에서 알 수 있는 사실은 Function() 원본 프로토타입 객체 역시 생성자 함수라는 것.
이부분은 JS를 공부할 때 이미 공부한 내용이지만, 좀 더 상세히 알게되었다.
자바스크립트는 특정 객체 안의 속성값이나 메소드를 접근하려고 할때 해당 객체에 접근하려는 속성이나 메소드가 없다면 [[prototype]]이 가리키는 링크를 따라 자신의 부모객체에서 찾는다. 또 없으면 부모객체의 [[prototype]]을 찾아가 찾는다. 결국 중첩 상속을 받아와도, 모든 메소드나 값에 접근이 가능하다는 뜻이다.
위의 사진과 같은 방식으로 메소드나 속성을 차례차례 찾아나간다.
객체 생성 방식은 3가지가 있다.
const developer = { name : 'kim' }
어차피 이렇게 불러도, Object() 생성자 함수로 객체를 생성함.
함수 선언식 function Person(){ } const newPerson = new Person(); // // 함수 표현식 => 익명함수로 할당해도, 기명함수로 변경됨 const people = function(number){ }
Person이나 people는 생성자 함수이므로, prototype 속성을 가지게됨.
Person.prototype === Person //true personMaker.prototype === personMaker//true
const newObject = new Object();
정리중