javaScript/jsDeepDive

생성자 함수, prototype, __proto__, constructor 관계 구조.

부엉이사장 2023. 6. 16. 16:58

# 기본 코드

const Dog = function (name) {
  this.name = name;
};

const dog1 = new Dog("무찌");

Dog라는 생성자 함수와 이 생성자 함수로 새로운 객체를 만들어, 식별자 dog1에 할당했다.

기본적인 생성자함수의 문법이다.

 

 

 

# Dog의 프로퍼티

console.dir(Dog);

위 코드를 쳐보면 

뭐 이렇게 생겼다. 우리가 주목할건 저 prototype이라는  프로퍼티다. 

저 prototype프로퍼티를 또 펴보면

요렇게 생겼고 constructor이라는 프로퍼티가 또 들어있다.

저 constructor이라는 프로퍼티를 또 펴보면

얘는 Dog라는 함수란다.. 읭? 우리가 처음 만든 Dog 생성자 함수다.

여기엔 또 prototype이 들어있다.

저 prototype프로퍼티를 또 펴보면

또 constructor 프로퍼티가 들어있고 거기엔 Dog함수가 들어있다.

어...

무한의 굴레..

이렇게 계속 바인딩의 무한의 굴레가 깊숙히 들어있다.

개발자도구에서 저짓을 반복하면 끝도없이 나오게된다.

 

 

# 이걸 그림으로 표현해보자~

이 생성자 함수 Dog안에 프로퍼티 prototype안의 constructor프로퍼티에는 다시 생성자 함수 Dog의 함수객체가 바인딩되어있다. 때문에 저렇게 무한의 지옥으로 들어가게 된거다.

그래서 아래와 같은 요상한 코드도 동작이 된다.

console.log(new Dog("무찌")); //Dog { name: '무찌' }
console.log(new Dog.prototype.constructor("도리")); //Dog { name: '도리' }

요렇게 Dog생성자 함수의 프로퍼티에 쭉쭉 접근해서 constructor에 바인딩된 Dog함수객체를 생성자함수로써 호출할수도 있음 ㅋ

 

 

# 그런데 저 그림도 조금은 틀렸다.

저 그림대로 되어있고 무한히 객체를 생성한다고 하면 스택오버플로우가 발생할것이다.

따라서 아래 그림같이 보는게 더욱 정확하다.

생성자 함수 Dog의 함수객체에는 prototype이라는 프로퍼티키가 식별자로써 있고 이 식별자를 참조하면 prototype객체에 바인딩이 되어있어 이 객체를 참조한다. 또 이 prototype객체안의 constructor 프로퍼티키가 식별자로써 Dog생성자 함수객체를 참조한다고 보면된다.

무한히 새로운 객체들을 생성하는게 아니라 저 그림처럼 바인딩만 두 번 연결된다고 보면됩니다.

 

 

 

 

 

# __proto__

const Dog = function (name) {
  this.name = name;
};

const dog1 = new Dog("무찌");

console.log(dog1.__proto__ == Dog.prototype); //true

일단 dog1은 객체다. Dog은 함수객체니까 dog1과는 다르다는걸 상기시키고..

이 dog1의 __proto__(프로퍼티?)는 Dog.prototype에 바인딩된 객체와 같다.

 

console.dir(dog1)

이 코드를 쳐보자.

이 객체에는 name이라는 프로퍼티에 '무찌'가 바인딩되어있다.

근데 어? __proto__는 프로퍼티로 없는데? 이거 에러아니야?

 

 

# __proto__의 정체

저 console.dir로 찍은 결과에서 [[Prototype]]을 펴보면

요렇게 생긴게 들어있다. 이 prototype에는 constructor로 이 객체를 만든 생성자 함수가 바인딩 되어있다.

정리하자면 dog1.__proto__로는 내부슬롯 [[Prototype]]에 접근할수 있고, (접근한다는말을 기억하샘)

이 내부슬롯에는 '이 객체를 만든 생성자 함수의 prototype객체가 바인딩'되어있다.

때문에 

console.log(dog1.__proto__ == Dog.prototype); //true

이 코드가 트루가 뜨는거다.

뭐 저 dir로 찍은것도 Dog.prototype이랑 똑같이생김..

객체 dog1의 내부슬롯 [[Prototype]]

 

Dog 생성자 함수의 prototype프로퍼티

 

 

 

이 전체적인 그림을 그려보면

 

 

메모리 구조에서 살펴보면

일단 저 코드의 전체적인 메모리 그림을 그려보면.. 참 보기 드럽게 그려놨지만 뭐..암튼 이렇다는 거다..

 

 

 

# __proto__는 뭐야그럼?

저 그림에서 __proto__는 분명? 프로퍼티키로써 참조를 한것같은데..? 그림에는 그냥 단순히 접근하는 방법정도로만 설명했다. 얘는 Object생성자함수의 프로토타입에 있는 프로퍼티다.

console.dir(Object.prototype);

찾았다. Object생성자 함수의 prototype프로퍼티에 접근자 함수로 __proto__가 정의되어있다.

그래서 접근한다고 표현을 헀던것이다.

모든 객체의 프로토타입체인의 최종지점에는 이 Object.prototype객체가 있기때문에 모든 객체에서 __proto__를 접근자함수로써 호출할 수 있는거다.

이걸 그림으로 그려보면

먼저 __proto__로 접근하면, dog1객체를 만든 생성자함수인 Dog의 prototype프로퍼티를 검색한다.

근데 없으니까 이 Dog.protype객체를 만든 Object생성자 함수의 prototype프로퍼티로 넘어간다.

여기엔 __proto__프로퍼티가 접근자프로퍼티로 존재한다!

 

 

# 프로토타입 검색 끊기(안됨)

Dog.prototype.__proto__ = "메롱";
console.log(dog1.__proto__); // 메롱안뜸
console.log(dog1.__proto__ == Dog.prototype); // true뜸

저 과정에서 Dog.prototype에서 __proto__를 검색했을때 없어서 상위 프로토타입 체인으로 넘어갔다.

근데 만약 Dog.prototype에 __proto__프로퍼티를 임의로 만들어준다면?

될줄알았는데 안된다 ㅋ

Dog.prototype.__proto__ = "메롱";
dog1.__proto__ = "뻴레렐렐";
console.log(dog1.__proto__);

이건 아예 dog1객체에 프로퍼티 __proto__를 만들어준거다.

예상했던 결과는 안나온다. 아예 객체에서 __proto__프로퍼티로 참조하려하면 Dog.prototype으로 덮어쓰나보다.

프로토타입체인 못끊기게하는듯.

근데 뭐 이건 중요치않다 ㅎㅎ

 

 

# __proto__를 원시값으로 바꾸는건 안되지만..

const Dog = function (name) {
  this.name = name;
};

const dog1 = new Dog("무찌");

dog1.__proto__ = {
  testValue: "메롱",
};

위 코드는 dog1의 __proto__라는 프로퍼티를 통째로 {testValue : '메롱'}인 객체로 바꿨다.

그런데?

console.log(Object.getOwnPropertyDescriptor(dog1, "__proto__")); //undefined뜸

dog1에 __proto__라는 프로퍼티가 없다? 

이건 즉 자바스크립트에서 아예 __proto__를 프로퍼티키로 못만들게 해논것같음.

또한 저 위에 코드에서 dog1.__proto__라는걸 참조하면

//원래대로라면 두개가 같아야하는데 분리됨
console.log(dog1.__proto__); // {testValue :"메롱"}
console.log(Dog.prototype); // {}

console.log(dog1.__proto__ == Dog.prototype); //false

갈아끼운 객체가 참조되긴함.

그래서 원래대로라면 생성자함수의 prototype과 dog1.__proto__로 호출해서 받아온 객체가 서로 같아야하는데 false뜸.

저 dog1객체는 이제 Dog생성자함수와 바인딩이 끊겨버린거임.

원리는 모르겠지만 희안하긴 하다 ㅋ

 

 

 

# dog1.__proto__.testValue = '머시기'이건 됨

dog1.__proto__.testValue = "머시기";
console.log(Dog.prototype.testValue); //머시기를 가져온다.

저 접근자함수로 반환된 객체에 testValue라는 프로퍼티를 추가한거라

이건 Dog.prototype객체와 연결을 끊지 않았음.

아마 이건 Object.prototype.__proto__프로퍼티에 set함수가 작동이 된것같다.

 

 

 

# 프로토타입 체인 끊어버리기

Dog.prototype.testValue_2 = "엉덩이"; //Dog생성자함수의 Prototype에 새로운 프로퍼티를 넣었음.
console.log(dog1.__proto__.testValue_2); //엉덩이, 
                        //dog1에서 __proto__로 Dog.prototype에 접근해서 테스트벨류2를 가져옴

dog1.__proto__ = {
  testValue: "메롱",
}; //객체를 통째로 갈아끼움

console.log(dog1.__proto__.testValue_2); //undefined, 이제 dog1에서 테스트 벨류 2를 가져올수 없음
console.log(dog1); //{ name: '무찌' } , 역시 __proto__라는 프로퍼티는 없음.

이제 저 코드에서 dog1은 Dog.prototype에 있는 프로퍼티나 메서드를 쓸 수 없다.

이런식으로 상속연결을 구현할수도 있긴함.

 

console.dir로 dog1객체를 뜯어보면

요러케 원래는 생성자함수 Dog랑 연결되었던 [[Prototype]]내부슬롯이 메롱이 담긴 객체로 바뀌어버렸다..

 

 

# 내가 생각하는 뇌피셜 __proto__접근자 프로퍼티 접근자함수 구조

const Object쩜prototype = {
  //머시기머시기머시기
  get __proto__() {
    //먼저 이 접근자함수를 호출한 객체에 this로 드가서
    //이 객체의 내부슬롯 [[Prototype]]에 있는 객체에 관리자권한으로 접근 및 반환
    return [[Prototype]]내부슬롯 객체
  },
  set __proto__(para){
    //para가 원시값이면 무시해~
    //para가 객체면 get __proto__에서 return para로 바꿔 ㅋ 
  }
};

이렇게 생기지 않았을까?