티스토리 뷰

앞선 포스팅에서 프로토타입을 살짝 맛봤다. 함수를 정의하게되면 개발자의 의지와는 상관없이 프로토타입이라는 객체가 생성되게되고, 함수를 그냥 메서드 사용하듯 호출할때는 크게 신경쓸필요없지만 생성자로 사용하게될경우엔 해당 함수를 생성자로 생성된 객체가 해당함수의 프로토타입을 바라본다는것까지 포스팅을 했다. 그리고 동일한 생성자로 생성된 각각의 다른 인스턴스들이 모두 한 프로토타입 객체를 바라보기때문에 인스턴스들이 공유하는 자원은 생성자의 프로토타입 객체에 정의하게되면 자바의 static 변수와같이 메모리를 절약할수있다는것까지 알게되었다.


그럼 여기서 본질적인 궁금증을 가져보자. 인스턴스들은 어떤식으로 자신의 생성자의 프로토타입 객체를 바라보게되는것일까?


function Undead(){

  this.name = "LichKing";

}


var undead01 = new Undead;


*강제사항은 아니지만 생성자함수는 대문자로 시작하는걸 원칙으로 한다.

*함수는 변수로 사용되기때문에 함수를 () 없이 사용하게되면 일반 변수처럼 사용한다고 했었다. 허나 new 가 앞에있을때는 ()가 없더라도 함수가 실행되게된다. 물론 new 가 붙는다는건 함수를 생성자로 객체를 생성한다는 뜻이고, 함수를 호출하는데 ()가 없으니 인자는 보낼수가없다. 지금처럼 인자없는 생성자의 경우에는 ()가 생략될 수 있다.


#06 포스팅에서 new 키워드가 있을때의 매커니즘에 대해 설명한적이있다. new가 앞에 붙어서 함수를 호출하게되면


1. 빈객체 생성

2. 함수실행

3. 함수 내부 this에 1에서 생성한 객체 대입

4. 해당 함수가 참조(객체)타입을 반환하면 원래 반환하는대로, 리턴문이 없거나 기본형타입을 반환하면 1에서 생성한 객체를 반환


이런식으로 실행이 된다. 여기서 한가지 빠진게 있는데 그 빠진부분이 인스턴스와 프로토타입의 연결끈이다.


1. 빈객체생성

2. 생성한 객체에 __proto__ 속성추가

3. 함수실행

4. 함수 내부 this에 1에서 생성한 객체 대입

5. 해당 함수가 참조(객체)타입을 반환하면 원래 반환하는대로, 리턴문이 없거나 기본형타입을 반환하면 1에서 생성한 객체를 반환


우리가 모르는사이 자바스크립트 엔진이 속성이 전혀없는 빈객체에 __proto__ 속성을 추가해주고있는것이다. 위 var undead01 = new Undead 이 구문을 살펴보면


function(){

var temp = {};

temp.__proto__ = Undead.prototype;

var returnObj = Undead.call(temp);


if(typeof returnObj == "object"){

return returnObj;

}else{

return temp;

}

}();


간단하게 짜본소스인데 대충 new 내부에서 이런식으로 돌아간다고 보면 될것같다.

__proto__ 속성으로 생성자의 prototype을 참조하기때문에 인스턴스의 속성을 탐색할때 생성자의 프로토타입 객체까지 연결할수있는 근거가 되는것이다.

 

이걸 좀더 심화적으로 사용하게되면 이것이 단순한 static 속성으로 사용하는것을 뛰어넘어서 상속까지 연결할 수 있게된다. 상속이 무엇인가? 부모클래스의 속성들을 그대로 이어받아 기존소스를 다시 타이핑할 필요없이 재사용이 가능하게끔하는것 아니겠는가?

 

가령 이런 소스라면 어떨까?

 

function Human(_height, _weight){

this.height = _height;

this.weight = _weight;

}

 

function Son(_age){

this.age = _age;

}

 

Son.prototype = new Human(180, 80);

 

var john = new Son(20);

 

john은 생성자함수인 Son의 프로토타입 객체를 바라볼것이다. 그런데 프로토타입객체라고해서 뭔가 특별하고 완전무결한 객체가 아니다. 단지 함수에서 삭제할 수 없는 기본 속성일것이다. 프로토타입에 속성 한두개를 추가한게아니라 가리키고있는 객체 자체를 바꿔버렸으니 Son() 생성자를 이용해 생성되는 객체들은 모두 height와 weight 속성을 갖게된다.

마찬가지로 Human.prototype = 다른객체; 형식으로 코딩을 하게되면 프로토타입체인은 계속해서 이어지게되며 이것이 자바스크립트 상속의 근간이 되며 자바스크립트를 프로토타입 중심 객체지향언어라고 부르는 이유인것이다.

 

*위에서 프로토타입 속성은 삭제가 되지않는다고 했는데 프로토타입이 null을 참조하게끔 하는건 가능하다.

 

이제 좀 더 머리아픈얘기에 들어가보자.

프로토타입에 대해 그냥 기본객체라고했지만 프로토타입 객체가 생성되면서 한가지 속성을 갖게된다. 그 속성명칭은 constructor. 듣자마자 꽤나 친숙한 이름인걸 알것이다.

constructor는 기본적으로 함수를 가리키고있는데 그 함수는 바로 '해당 프로토타입을 갖고있는 함수'이다.

 

function TestFunc(){}

TestFunc.prototype.constructor === TestFunc;

 

이제부터 머리가 엄청 복잡해진다. 생성자 함수에서 new를 사용해 생성된 인스턴스는 __proto__ 속성으로인해 자신의 생성자의 프로토타입객체와 인연의 끈을 놓지않게된다고 했다. 허나 __proto__는 함수의 prototype을 참조하는게 기본이고 그말은 당연히 __proto__를 통해 constructor 속성에도 접근이 가능하다는 말이 된다.

 

var obj = new TestFunc;

obj.constructor === TestFunc;

obj.constructor.field = "hello world";

alert(TestFunc.field);

 

이 얼마나 변태같은 상황인가?

자 이제 생각을 정리해보자. 어쩌면 아니 분명히 더 어지러워질것이겠지만.

이제 이걸 이해해야한다.

'모든 객체의 부모는 Object 객체이고, 모든 객체의 생성자는 Function이다.'

객체라면 무릇 __proto__속성을 갖고있게되고 __proto__속성이 최종적으로 가리키는 객체는 Object객체이다. 크롬브라우저에서 빈 객체를 하나 생성해 __proto__가 어딜 가리키는지 확인해보자.

 

var obj = {};

console.log(obj.__proto__);

console.log(obj.__proto__.__proto__);

 

결국 최종적으로 __proto__의 끈이 끊어지는곳은 Object 객체이다.

이것도 확인해보자.

 

console.log(obj.constructor);

console.log(obj.constructor.constructor);

 

obj의 생성자는 Object 함수이고, 함수도 결국 객체이기에 constructor.constructor를 찍게되면 Function이 나타난다. Function이 생성자라는건 결국 Function도 함수라는 뜻이기때문에 __proto__처럼 null이 뜨진 않지만 최종적으론 계속 Function이 Function을 가리키게된다.

 

console.log(obj.constructor.__proto__);

 

Function도 '함수객체'다. 객체라는건 prototype 체인이 연결되어있다는것이고, Function 함수의 prototype을 가리키게 될것이다.

그런데 확인해보면 알수있겠지만 기본 프로토타입객체는 Object 객체인데 Function은 프로토타입이 익명함수로 되어있다.

이미 함수가 객체고 객체가 함수란걸 알고있으니 예상하지못한 결과에 당황은 할 수 있을지언정 '이런게 가능한가?' 라는 의문은 품지말자.

댓글
댓글쓰기 폼