반응형

2편에 이어서 하겠습니다. 

2편 마지막에 생성자함수.prototype = A 를 하면 앞으로 그 생성자 함수로 만들어지는 모든 객체(인스턴스)는 프로토타입으로 A를 가진다고 했었죠? 오늘은 그 점은 코드로 다시 봅시다.

 

const person = {
  talking(){
      console.log(`Hello I'm ${this.name}`);
  }
};

function Nicolas() {
	this.name = 'nicolas'
}
	
Nicolas.prototype = person;		// 생성자함수의 prototype 프로퍼티에 person을 넣습니다.

let nico = new Nicolas();

nico.talking(); 			// Hello I'm nocolas

여러분이 기억해야 할 것이 있습니다. 

1. 자바스크립트의 모든 함수에는 자동으로 prototype이란 프로퍼티가 존재한다.

2. 생성자 함수에 person을 담고,  그 생성자함수로 생성된 모든 객체는 person을 프로토타입으로 가지게된다.

 

 

1번에서 모든 함수에는 prototype이라는 프로퍼티가 존재한다고 했습니다. 그럼 만약에 자동으로 생기는 거니깐 기본값으로는 어떤 객체가 넣어질까요? 

위 코드에서는 사용자가 Nicolas.prototype = person; 하는 순간 prototype에는 person이 넣어지죠.

그렇다면 그 전에 있는 기본값은 무엇일까요? 왜냐면 함수가 만들어질때 prototype 프로퍼티도 자동으로 생성된다고 하니 그럼 처음에는 기본으로 어떤 객체가 prototype프로퍼티에 들어가 있을까? 의문이 있을 수 있겠죠?

 

기본값으로는 바로 constructor함수가 들어 있으며 이 안에는 자기자신인 Nicolas()가 들어있습니다.

즉 다음이 성립됩니다. 

Nicolas.prototype = { constructor : Nicolas }  

따라서 다음도 성립하죠  

console.log(Nicolas.prototype.constructor == Nicolas)      // true

 

자 기본값은 저러한데, 만약에 위의 코드에서처럼 Nicolas.prototype = person; 을 넣어주면 어떻게 될까요? 

당연히 기본값들이 다 사라지죠. 이젠 Nicolas의prototype에는 person객체 하나 밖에 존재하지 않습니다. 

 

그래서 우리는 기본값을 유지해주기 위해 Nicolas.prototype = person 처럼 prototype의 전체를 덮어씌우지 않고, 이렇게 합니다. 

Nicolas.prototype.person = person; 

이렇게 하면 기존의 prototype.constructor 는 사라지지 않고 계속 존재합니다.

 

기존 constructor가 지워지지 않으면 우리는 생성자함수로 인해 만들어진 인스턴스를 이용해서 똑같은 객체를 찍어낼 수 있습니다. 

function Coco() {
	this.say = function(){
    	console.log("I'm Coco");
    }
 }
 
 const coco1 = new Coco();
 
 const coco2 = new coco1.constructor()		// 생성자함수가 아닌 coco1인스턴스로 만들어봅시다.

 console.dir(coco1) 		
 console.dir(coco2) 				// 둘의 구조가 똑같습니다.

 

자 그러면 다음 코드를 봅시다.

function Coco() {
	this.say = function(){
    	console.log("I'm Coco");
    }
 }
 
 const coco1 = new Coco();
 const coco2 = new coco1.constructor()
 
 Coco.prototype = {};			// 여기서 Coco.prototype 의 기본값을 빈객체로 지워봅시다.

 console.log(coco1.constructor);	// 이미 생성된 인스턴스들은 기본값을 가지고 있습니다.
 console.log(coco2.constructor);
 
 const coco3 = new Coco();		// 다시 새로운 인스턴스를 찍어봅시다.
 
 console.log(coco3.costructor);		// 역시 빈객체로 교체된 후 만들어진 인스턴스들은 undefined로 나옵니다.

위 코드각 보여주는 점은 Coco.prototype = {} 가 선언되기 전에 Coco생성자 함수로 생성된 인스턴스들은 기본값을 가지고 있다는 것 입니다. 

 

그럼 다음은 어떨까요?

function Coco() {
	this.say = function(){
    	console.log("I'm Coco");
    }
 }
 
const coco1 = new Coco();

const coco2 = new coco1.constructor();
 
Coco.prototype.name = 'coco';		// 빈객체로 대체하지 않고 name이란 프로퍼티를 추가합니다.

console.log(coco1.name);		// coco
console.log(coco2.name);		// coco

위와는 좀 다르게, 그 전에 만들어진 coco1, coco2 모두 name이라는 프로퍼티를 가지고 있고 값도 올바른게 가지고 있습니다. 

 

그 이유를 알려면 참조 관계에 대해서 알아야 합니다. 그리고 자바스크립트 선언문의 특징에 대해서도 말이죠

C나C++ 를 배우신 분이라면 참조에 대해서 잘 아실 겁니다. 포인터개념을 배우기 때문이죠 

여기서 참조라는 것을 메모리상의 포인터로 설명하자면 너무 길어지고 대충 간략하게만 설명하겠습니다.

 

"A가 B를 참조한다." 라는 뜻은 A가 B의 자원을 사용 할 수 있다는 말이며, 동시에 B가 바뀌면 A도 똑같이 바뀐다는 의미입니다.  상속의 의미와 같죠. 부모에 있는 자원이 바뀌면 그것을 상속하는 자식쪽의 자원도 바뀝니다. (물론 자식만 가지고 있는 자원은 바뀌지 않겠죠? 상속과 관련이 없으니깐요.) 상속도 참조의 개념으로 이루어지는 거니깐요. 

 

비슷하게 참조 복사와 완전 복사라는 말도 있습니다. 

A가 B를 참조복사 했다. >> B가 수정되면 A도 똑같이 수정됩니다.

A가 B를 완전복사 했다. >> 처음 상태는 서로 똑같지만 B가 이후 수정이 되어도 A는 처음 상태의B를 그대로 간직합니다. 서로 독립적인 데이터가 된 것이죠.

 

자 이제 참조에 대해 이해가 되었으면 자바스크립트의 선언문의 특징에 대해 간단히 알아보겠습니다.

자바스크립트에서는 특이하게도 없는 변수를 호출해도, undefined라는 값이 대입되어 나옵니다. 

그럴정도로 자바스크립트 엔진은 없는 변수를 호출해도 최대한 그 변수를 어떻게든 만들어서라도 뭔가 나오게 합니다. 

그래서 Coco.prototype.name = 'coco' 라는 뜻은 두 가지로 해석됩니다.

1. prototype안에 name에 접근해서 값을 얻어와라

2. prototype안에 name이 없다면 지금 만들어라!

 

따라서 위의 Coco.prototype.name = 'coco'는 이렇게 해석됩니다. 

1. Coco로 간다.

2. prototype이 있는지 찾는다 없으면 생성한다. 여기서는 있으니 거기로 들어간다.

3. name을 찾는다. 없으면 생성한다. 여기서는 name이 없으니 지금 생성한다!

4. 그name의 데이터로 'coco'를 저장한다.

5. 끝 

 

다시 그럼 문제의 두 코드를 봅시다.

Coco.prototype = { }                        // 빈객체로 대체됨

Coco.prototype.name = 'coco'            // prototype안에 name프로퍼티를 또 추가해서 그 안에 coco 를 저장

 

문제의 요지는 

Coco.prototype = { }  를 썼을 때는 이 코드 전에 만들어진 객체의 prototype이 빈객체이지 않고 그 전의 값으로 기본값을 계속 가지고 있었습니다. 이것만 보면 이미 만들어진 객체에 대해서는 바뀌지 않는 것처럼 보입니다.

허나 Coco.prototype.name = 'coco' 를 썼을 때는 이 코드 전에 만들어진 객체에 대해서도 반영이 되어서 name값을 가지고 있었죠.

 

이 이유는 Coco.prototype을 Coco의 생성자로 생성된 모든 인스턴스들의 prototype이 참조하고 있기 때문입니다.

즉 new를 해서 새로운 인스턴스를 만들 때마다 Coco의 prototype를 참조복사해서 가지고 가는 것이죠. 

따라서 순서가 어떻게되든 Coco.prototype이 수정되면 그것을 참조하는 모든 인스턴스들의 prototype도 수정되는 겁니다. 그런데 위에서 Coco.prototype = { } 를 했을땐, 따라가지 않았죠? 그대로 기본값을 계속 들고있었습니다.

 

그 핵심 이유는 그 전의 참조를 끊고, 새로운 참조를 생성한 것이기 때문입니다.

그 전의 참조는 coco1, coco2 의 prototype(= __proto__)들은 Coco.prototype의 기본값을 참조 하고 있었습니다.

하지만 문제의 Coco.prototype = { } 를 만나는 순간 기존의 prototype이 { } 값으로 대체됩니다.

사실상 메모리구조로 이해하면 더 쉬운데, 기존에는 Coco.prototype이 0x123 주소인 메모리로 연결되어 있다가 이제 더이상 이 메모리와 연결이 끊어지고 0x321인 메모리 주소로 ( { } 빈객체가 있는 메모리 ) 로 이어진다는 것이죠. 

 

따라서 Coco.prototype = { } 이후에, 만들어진 객체들은 기본값이 사라지고 빈객체만들 들고있습니다. 

하지만 그 전에 만들어진 객체들은 아직도 0x123 주소에 참조가 이어지고 있는 것이죠. 그래서 계속 기본값을 들고 있는 겁니다.

 

다시 설명하자면

Coco.prototype.name은 prototype에 값을 수정한것이 아니라서, 일단은 기존의 prototype이 있는 메모리 주소로 갑니다. 그리고 그 안에 name를 새로 만드는 것이죠. 따라서 참조가 끊겼다가 다시 만들어지는게 아닌, 그냥 원래 참조했던 메모리 방 안에서, 새로운 방을 하나 만든 것 뿐이죠. 그래서 Coco.prototype.name = 'coco' 코드 이전에 만들어진 객체든, 그 이후에 만들어진 객체든 상관없이 참조를 그대로 따라가는 겁니다. 

 

반면에 Coco.prototype = { }를 만나게 되면, 기존 참조길을 한번 끊고, 새롭게 다른 곳으로 ( { } 빈객체가 있는 방으로) 참조가 이어지기 때문에, Coco.prototype = { } 코드 전에 만들어진 객체들은 이전 참조길을 가지고 있어서 기본값을 들고있지만, Coco.prototype = { } 코드 이후에 만들어진 객체들은 새로운 참조길을 가지고 있어서 빈객체를 들고 있게 되는 겁니다. 

 

휴 설명이 제가 잃어도 좀 난해하네요.. 죄송합니다. 이해가 어려우시면 댓글 부탁드려요. 저도 많이 부족한것 같습니다.

오늘의 포스팅은 여기까지 하겠습니다.

 

 

 

 

 

 

 

 

*틀린 점이 있다면 댓글로 달아주세요. 아직 배우는 학생이랍니다. 
*질문도 댓글로 적어주시면 답변할 수 있는 한도 내에서 답변해드리겠습니다. 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts