hant422
36
2020-10-15 22:03:17
17
282

Javascript의 prototype의 상속 관련 질문


안녕하세요, Javascript의 기본부터 다시 공부 중인 신입 프론트 개발자입니다.

최근 prototype과 객체 상속에 관해 공부 중인데, 도저히 이해가 가지 않는 부분이 생겨 고수님들께 조언을 구하고자 글을 올리게 됐습니다.


질문1. prototype이 null인 객체는 Object를 상속하는가?


생성자 함수를 사용해 객체를 생성하면 생성자 함수의 prototype을 상속받는 것은 이해하고 있습니다.

function C() {};

var o = new C();

var test1 = Object.getPrototypeOf(o) === C.prototype; // true
var test2 = Object.getPrototypeOf(o) === Object.prototype; // false


허나 생성자 함수의 prototype이 null일 경우 왜 Object의 prototype을 상속 받는지 이해가 가지 않습니다.

function C() {};
C.prototype = null;

var o = new C();

var test1 = Object.getPrototypeOf(o) === C.prototype; // false
var test2 = Object.getPrototypeOf(o) === Object.prototype; // true


질문2. for...in 반복문은 Object.prototype에 접근하지 않는가?


for...in 반복문은 객체의 상속된 프로퍼티 또한 자신이 소유한 프로퍼티로 열거하는 것으로 알고 있습니다.

function C() {};

C.prototype.a = function() {
  return 'a';
}

C.prototype.b = function() {
  return 'b';
}

var o = new C();

for (var key in o) {
  console.log(key);
}

// a
// b


하지만 Object의 프로퍼티를 상속받은 경우, for...in 반복문은 Object.prototype에 접근하지 않습니다.

var o = {};

var test1 = Object.getPrototypeOf(o) === Object.prototype; // true

for (var key in o) {
  console.log(key);
}

// 아무것도 출력되지 않음


그렇지만, 금기의 방법을 사용해 Object의 prototype에 프로퍼티를 추가하면, 그것은 출력합니다.

Object.prototype.a = function() {
  return 'a'
}

var o = {};

for (var key in o) {
  console.log(key);
}

// a


MDN 문서에 보면 Object의 prototype 내부에는 많은 프로퍼티들이 존재하던데, 왜 그것에는 접근하지 않는건지 이해가 가지 않습니다.

1
  • 답변 17

  • Frudy
    6k
    2020-10-15 23:13:29


    그리고 두번째 질문은 이 캡처가 증거물인거같아요.

    Object.prototype 밑에있는것들 몽땅다 enumerable이 false네요?

  • hant422
    36
    2020-10-15 23:24:39

    질문 1번의 내용은 Frudy님의 두번째 댓글에대한 질문이 맞습니다.

    상속이라는 단어 표현이 prototype의 얕은 복사가 아니라 깊은 복사를 말한 것이 아닙니다.

    1. o.prototype이 C.prototype의 래퍼런스를 참조한다.

    2. C.prototype.prototype이 Object.prototype의 래퍼런스를 참조한다.

     위 두가지 사항은 이해하고 있습니다.

    하지만 궁금한점은 C.prototype이 null이 됐을 때, o.prototype --> null === Object.prototype 이라는게 이해가 안됩니다.


  • hant422
    36
    2020-10-15 23:30:20

    Frudy님 두번째 질문 답변 정말 감사드립니다. 해당 부분에 대해서는 전혀 알지 못하고 있었습니다. 공부할게 또 하나 생겼네요 ㅎㅎ 감사합니다.

  • cat11
    428
    2020-10-16 08:54:50

    typeof null ==> "object"
    링크

  • hant422
    36
    2020-10-16 11:24:35 작성 2020-10-16 11:40:44 수정됨
    function C() {};
    C.prototype = null;
    
    var o = new C();

    위 코드에서 o.prototype와 C.prototype이 한 래퍼런스를 바라보고 있고, C.prototype만 체이닝이 끊긴 것이라서 두 prototype이 같지 않다고 이해하면 되는건가요?

    아니면 prototype이 null인 생성자 함수로 객체를 생성할 때는 자동으로 Object.prototype과 연결되게 되는 구조로 이해하면 되는건가요?

    var test1 = Object.getPrototypeOf(o) === C.prototype; // false
    var test2 = Object.getPrototypeOf(o) === Object.prototype; // true

    전자의 경우로 이해하면, 위 결과에서 o.prototype.prototype === Object.prototype이 true가 되야한다고 생각하는데, o.prototype === Object.prototype이 왜 true인지 궁금합니다.

    제 질문실력이 부족해 도와주시려는 많은 분들께 혼선을 드리는것 같아 죄송합니다. ㅜㅜ

  • hant422
    36
    2020-10-16 13:36:32 작성 2020-10-16 13:39:30 수정됨

    1. 아 제가 혼용해서 잘못 사용했네요.. 죄송합니다. __proto__를 말하는게 맞습니다. __proto__가 공식적으로 모든 엔진에서 사용되는 것이 아니라 알고있어서, 가급적 안쓰려고 했습니다. 그러다보니 Object.getPrototypeOf(o)라고 쓰기 너무 길어서, 그냥 prototype이라고 말하는 실수를 해버렸습니다 ㅜㅜ prototype 링크를 말하고 싶었습니다...

    2. 배운적 있습니다. Javascript에서 객체를 변수에 바인딩할 때, Heap의 주소를 할당하는 것도 이해하고 있습니다.

  • hant422
    36
    2020-10-16 14:08:43

    위 수정내용으로 다시 질문은 정리하자면  o.__proto__는 C.porototype의 엔드 포인트를 바라봐야 한다. 하지만 C.prototype이 null일 경우 o.__proto__가 null이 될거라 생각했습니다. (Object.create(null)과 같은 결과)


    var test1 = Object.getPrototypeOf(o) === C.prototype; // false

    하지만 위의 결과는 생각과 달랐습니다. 그래서 두가지 생각을 했습니다.


    첫번째 생각

    C 생성자 함수를 선언할 때, C의 prototype 객체가 생기고 그것의 주소를 별도의 공간에 저장하고 기억한다. 그리고 C를 사용해 o를 생성하면 별도로 저장된 주소를 o.__proto__에 할당한다. C의 prototype은 null로 재할당 됐으니, C.prototype과 o.__proto__와는 다르다.


    두번째 생각

    prototype이 null인 생성자 함수로 객체를 만들면 자동으로 일반 Object를 생성한 것으로 인지하고, Object.prototype과 연결한다.


    2번이 정답이라면 딱히 이견이 없지만, 1번이 정답이라면 추가적인 의문 사항이 생깁니다. 1번 상황에서도 기존과 그대로 o.__proto__.__proto__ === Object.prototype이 맞다고 보는데, 위의 결과는 o.__proto__ === Object.prototype이기 때문입니다.

  • Frudy
    6k
    2020-10-16 18:22:10

    전혀 죄송하실일아니에요!

    집요하게 모르는걸 탐구하는자세

    정말좋은거에요. 저도 그부분은 오히려 배워야겠네요!

  • Frudy
    6k
    2020-10-16 19:04:34



    변수 a b에 동일한 주소값이 할당됬어여.

    둘다 array의 주소값을 가리키고있죠.




    이렇게 a에 다른값을 할당해도,

    b는 아무런 영향이 없는거에요.


    변수는 뭔가를 저장할 수 있는곳이에요.

    a에 저장한거랑 b랑 저장한것이 같을 뿐이지, 

    a에 다른값을 할당한다고 b에 다른값이 저장되는것이 아니에요.

  • Frudy
    6k
    2020-10-16 19:11:02

    그래서, 


    o.__proto__ 에 저장된 주소값과 

    C.prototype에 저장된 주소값이 같아여.


    ==햇을 때 true나오는게 그 증거에요.


    둘다 서로 같은 주소값을 저장하고있죠?

    그치만 한쪽에 다른값을 저장한다고 해서

    다른한쪽에도 똑같이 저장되는게 절대 아니에요.

  • hant422
    36
    2020-10-16 20:59:05 작성 2020-10-16 21:07:17 수정됨

    퇴근하고 확인하느라 늦었습니다.말씀은 제가 생각했던 첫번째 생각이 정답이라고 이해해도 될까요?


    첫번째 생각

    C 생성자 함수를 선언할 때, C의 prototype 객체가 생기고 그것의 주소를 별도의 공간에 저장하고 기억한다. 그리고 C를 사용해 o를 생성하면 별도로 저장된 주소를 o.__proto__에 할당한다. C의 prototype은 null로 재할당 됐으니, C.prototype과 o.__proto__와는 다르다.


    이 생각이 정답이라고 생각하기 힘들었던 이유는 다음 두가지가 이해되지않아서 입니다.

    function C() {};
    C.prototype = null;
    
    var o = new C();
    
    var test1 = Object.getPrototypeOf(o) === C.prototype; // false
    var test2 = Object.getPrototypeOf(o) === Object.prototype; // true


    1. C.prototype을 null로 할당하는 로직이, C를 사용해서 o를 생성하는 로직보다 먼저 나옵니다. null로 할당되기 전의 C.prototype의 엔드 포인트의 주소를 어떻게 저장하고 있다가 o를 생성할 때 할당할 수 있을까요?

    2. C.prototype을 null로 초기화하지 않았다면, C로 생성한 객체는 다음의 결과를 뽑습니다

    o.__proto__.__proto__ = Object.prototype

    C.prototype이 null로 할당되면서 엔드포인트에 영향이 없었다면, null로 할당한 후에도 위의 결과는 동일하게 나와야합니다. 하지만 위의 결과가 동일하게 나온다면 밑의 결과가 나오는 것이 이해가가지 않습니다.

    var test2 = Object.getPrototypeOf(o) === Object.prototype; // true


  • hant422
    36
    2020-10-16 21:05:34
    Javascript의 객체는 Heap에 할당되고, 각 변수는 그 주소를 바인딩하는것은 알고있습니다. 하지만 참조되는 곳을 모두 잃은 Heap 메모리는 GC에 의해 해지됩니다. 여기서 C.prototype이 null로 할당되는 로직이 먼저 나오는데, Heap의 주소값을 어디서 계속 참조하고 있는건지 궁금합니다. 그걸 참조하고 있어야 GC에 의해 처리되지 않으며, 새로운 객체를 생성할 때 __proto__에 상속시켜줄 수 있으니까요.
  • hant422
    36
    2020-10-16 21:35:43

    위의 두가지 이해가지 않는 점 때문에 두번째 생각이 정답인지 질문한 것이였습니다.


    두번째 생각

    prototype이 null인 생성자 함수로 객체를 만들면 자동으로 일반 Object를 생성한 것으로 인지하고, Object.prototype과 연결한다.


    그래서 질문도 "prototype이 null인 객체는 Object를 상속하는가?" 였습니다..

    이게 질문의 핵심이였는데,, 저의 부적절한 언어 선택과, 미숙한 질문 실력으로 빙빙 돌았네요..

  • Frudy
    6k
    2020-10-16 23:24:38 작성 2020-10-16 23:30:41 수정됨
    오.. 저보다 잘하시네요.
    제가 질문이해를 잘못해서 요상한 댓글을 달았어요.

    저도 이부분은 궁금해요.

    일단 제가알고있는 지식은,

    객체를 생성할 때 생성자의 prototype과 연결한다 에요. (출처 : 오키에서 추천받은 어느 책)

    그런데 질문자님 말씀대로,

    C.prototype에 널이 저장되어있는데,
    이 코드가 먼저나오는데,

    당연히 객체생성시 생성자의 prototype에 할당됬던걸 받을탠데

    오...그렇죠. 맞아요. 이상해요.

    하지만, 컴퓨터는 거짓말을 하지않아요.


    저는 이런경우, 사실만을 기억하는 편이에요.

    생성자의 프로토타입 프로퍼티에 널을 할당해도, 인스턴스는 프로토타입 체이닝이 끊어지지않는다.

    o.__proto__에 저장된값은 여전히 유효하다.
    이거에요.

    생각되는 가능성은 하나네요.


    생성자의 프로토타입 프로퍼티가 널이 할당되는 시점과

    인스턴스의 프로토 프로퍼티가 생성자의 프로토타입 프로퍼티에 연결되는 시점이 언제인지 알아볼 필요가 있어요.

    저랑 질문자님이 생각하는것처럼,
    객체가 "생성"될 때 생성자의 프로토타입 프로퍼티에 저장된 주소값을 가져온다면,
    분명 객체는 프로토타입 체인이 끊어져야해요.

    하지만 그러질않았어요.


    제 첫 번째 생각은, 시점을 찾아보는거에요.

    이건저도 궁금하네요.
  • Frudy
    6k
    2020-10-16 23:31:32

    질문자님의 좋은질문덕분에 저도 모르는거 하나 발견했습니다. 감사합니다~~

    좋은주말보내세요!

  • hant422
    36
    2020-10-17 00:40:19 작성 2020-10-17 01:13:25 수정됨

    시점을 이야기 하시니까, Javascript의 변수 호이스팅과 연관이 있을 수도 있다는 생각이 문득 드네요.

    댓글 수정하신걸 잠깐 봤는데, 두번째 생각의 근거..라고 하기에는 미미하지만 다음과 같습니다.

    1. 호이스팅이나 여러가지 요건이 작용해서 C.prototype이 null로 할당되기 전의 주소를 상속받았다고 가정한다면, 다음 조건이 부합하지 않습니다.

    Object.getPrototypeOf(o) === Object.prototype // true

    o.__proto__가 C.prototype의 원본을 상속받았다면  o.__proto__.__proto__ === Object.prototype 처럼 결과가 나와야하지, 위의 결과처럼  o.__proto__ === Object.prototype 이 나오면 안됩니다.


    2. 1번을 뒷받침하는 근거입니다. 할당의 시점 문제가 거론되는 이상 정확한 실험이라고는 할 수 없습니다.

    function C() {}
    
    C.prototype.funcA = function () {
      return "a";
    };
    
    C.prototype = null;
    
    const o = new C();
    
    console.log(typeof o.funcA); // undefined


    3. 1번과 이어지는 근거인데, 객체의 __proto__가 Object.prototype과 같은 경우는 다음 두가지입니다.

    const temp1 = {};
    const temp2 = new Object();

    근거라고 하기에는 너무 미미합니다. 하지만 C.prototype을 상속하지 않았다고 말할 수는 있을 것 같습니다.

    Frudy님 같이 고민해서 저도 좋았습니다. 시작한지 얼마 안됐지만 좋은 사이트네요 ㅎㅎ 질문2 에대한 답변은 정말 몰랐는데 감사합니다. 행복한 주말 보내세요~!

  • hant422
    36
    2020-10-17 01:44:11 작성 2020-10-17 01:45:47 수정됨

    이 현상은 호이스팅 같은 선언 타이밍과 무관하고, 두번째 생각(prototype이 null인 생성자로 객체를 생성하면, 일반 Object를 생성한 것으로 인지하고 Object.prototype을 상속받는다)이 맞다는 실험을 한번 해봤습니다.

    실험 엔진은 node v10.18.0을 사용했습니다.

    function C() {}
    
    C.prototype.funcA = function () {
      return "a";
    };
    
    /* o1은 C.prototype을 제대로 상속받는다 */
    const o1 = new C();
    console.log(typeof o1.funcA); // function
    
    C.prototype = null;
    
    /* o2은 C.prototype을 상속받지 못한다 */
    const o2 = new C();
    console.log(typeof o2.funcA); // undefined
    
    /* o1과 o2는 서로 다른 __proto__를 상속받고 있다 */
    console.log(Object.getPrototypeOf(o1) === Object.getPrototypeOf(o2)); // false
    
    const o3 = {};
    
    /* o3와 o2는 같은 __proto__를 상속받고 있다. */
    console.log(Object.getPrototypeOf(o2) === Object.getPrototypeOf(o3)); // true


  • 로그인을 하시면 답변 을 등록할 수 있습니다.