자바스크립트 네이티브 프로토타입

조회수 532회

https://ko.javascript.info/function-prototype

이 글 ( https://ko.javascript.info/function-prototype ) 에서 함수의 prototype 프로퍼티는 [[Prototype]]과는 다르고, new를 호출할 때 만들어지는 새로운 객체의 [[Prototype]]만을 설정한다고 했었는데요. 이 글의 https://ko.javascript.info/native-prototypes 네이티브 프로토타입 변경하기를 보고 확인해보니, String.prototype에 새로운 프로퍼티를 추가해 줄 경우에는 이미 생성된 객체의 [[Prototype]]에도 영향을 미치는 것 같습니다. 기존에 생성되어 있는 객체의 [[Prototype]]에는 영향을 주지 못하고, 새로 생성되는 String 객체의 [[Prototype]]에 대해서만 영향을 미쳐야 하는 것이 아닌가요?

제가 잘못 이해한 부분이 있을까요? 왜 이러한 결과가 나오는지 이유가 궁금합니다.

  • (•́ ✖ •̀)
    알 수 없는 사용자

2 답변

  • 각 글을 따로 읽었을 때는 해당 상황에 맞는 설명만 써 있어서 문제가 없는데

    이 두 글을 같이 비교하면서 읽게 되면 뭔가 혼동이 오는 것 같네요.


    https://ko.javascript.info/function-prototype

    이 첫 번째 글은 Function을 new 키워드로 인스턴스화 했을때 만들어지는 객체가 생성자 함수와 prototype 프로퍼티를 통해 어떻게 관계가 맺어지는지를 설명하고 있습니다.

    여기서 prototype 변경하기 라고 나오는데,

    function Rabbit() {}
    Rabbit.prototype = {
      jumps: true
    };
    

    코드상으로 보면 기존의 prototype에 있는 값을 다른 값 ({jumps:true}) 으로 대체하고 있습니다.

    function Rabbit() {}
    console.log(Rabbit.prototype);    // {constructor: ƒ}
    Rabbit.prototype = {jumps: true};
    console.log(Rabbit.prototype);    // {jumps: true}
    

    이 변경을 후술할 내용과 혼동하지 않기 위해 prototype 재정의 라고 합시다.


    https://ko.javascript.info/native-prototypes

    두 번째 글입니다. 네이티브 객체를 예를 들어 prototype chain 이 어떻게 동작하는지 설명하고 있습니다.

    여기서도 변경이라는 용어를 사용하고 있습니다. 하지만 변경 방식은 좀 다른데요.

    String.prototype.show = function() {
      alert(this);
    };
    

    기존의 prototype 프로퍼티에 show라는 프로퍼티(함수)를 추가 했네요. 이건 prototype에 다른 값을 재할당 한 것이 아니므로 기존 String.prototype에 존재했던 다른 프로퍼티(함수)들은 유지되고 show라는 함수만 추가된 상황입니다.

    function Rabbit() {}
    console.log(Rabbit.prototype); // {constructor: ƒ}
    Rabbit.prototype.jumps = true;
    console.log(Rabbit.prototype); // {jumps: true, constructor: ƒ}
    

    이 변경은 prototype 확장이라고 합시다.


    prototype 재정의prototype 확장은 둘 다 prototype을 변경시키는 것은 맞으나

    prototype 재정의를 할 경우 기존 생성자 함수의 프로토타입 속성들을 잃어버립니다.

    인스턴스화 하여 생성된 객체는 생성 당시의 생성자 함수의 prototype을 참조 하고 있습니다.

    하지만 생성자 함수의 prototype 재정의 이 후에 인스턴스화 하여 생성된 객체는 재정의 이후의 prototype만을 참조할 뿐 재정의 이 전의 객체는 접근할 수 없죠.

    이런 성질 때문에 글에서 딱 한 번만 쓸 수 있는 이용권 이라는 표현을 쓴 겁니다. (백업을 해 두지 않는다는 가정 하에...)

    다음의 예제를 보시면 이해에 도움이 되실 겁니다.

    function Rabbit() {}
    Rabbit.prototype = {jumps: true};
    
    // 첫 번째 토끼
    var rabbit1 = new Rabbit();
    console.log(rabbit1.jumps);     // true
    
    // 생성자 함수의 prototype이 재정의 되었지만
    // 이미 그 전에 만들어진 인스턴스 객체가 참조하는건
    // 재정의 이 전 프로토타입 객체이므로 영향을 받지 않는다.
    Rabbit.prototype = {shortEars: false};
    console.log(rabbit1.jumps);     // true
    
    // 두 번째 토끼
    // 위에서 prototype이 재정의 되었으므로
    // jumps는 정의되어 있지 않다.
    // 대신 재정의 된 속성 값은 제대로 들어가 있다.
    var rabbit2 = new Rabbit();
    console.log(rabbit2.jumps);     // undefined
    console.log(rabbit2.shortEars); // false
    
    // rabbit2가 참조하는 프로토타입을 따로 저장하고 (위에 언급한 백업)
    // 다시 생성자 함수의 prototype을 재정의 해 보자.
    var temp = Rabbit.prototype;
    Rabbit.prototype = {redEyes: true};
    
    // rabbit2또한 rabbit1이 그랬던 것처럼 영향을 받지 않는다.
    console.log(rabbit2.shortEars); // false
    console.log(rabbit2.redEyes);   // undefined
    
    // 하지만 재정의 이전에 prototype이었던 temp를 변경하면
    // rabbit2에도 반영됨을 확인할 수 있다. 
    // 기존 속성값을 변경하거나,
    temp.shortEars = true;
    console.log(rabbit2.shortEars); // true
    
    // 새로운 속성을 추가해도 반영된다.
    temp.shortTail = true;
    console.log(rabbit2.shortTail); // true
    

    참고로 한 객체가 참조하는 프로토타입은 그 객체의 __proto__ 속성을 통해 확인할 수 있습니다.

    위의 예제에서 나온 값들을 가지고 비교해 보면

    // 프로토타입으로 참조하는 객체가 서로 다르므로 false.
    rabbit1.__proto__ === rabbit2.__proto__ // false
    
    // 서로 같으므로 true.
    temp === rabbit2.__proto__              //true
    

    이렇게 나오는데, 이는 생성자 함수의 프로토타입이 이미 인스턴스화 된 객체에 영향을 미칠 수 있다는 증거이기도 합니다.

    이제 prototype 확장을 예시로 살펴보고 마무리하겠습니다. 지금까지 정확하게 이해하셨다면 이건 쉬울거에요.

    function Rabbit() {}
    
    // 일단 처음에는 prototype 재정의
    Rabbit.prototype = {jumps: true};
    
    // 확장 이 전에 인스턴스 객체를 하나 만든다.
    var rabbit1 = new Rabbit();
    
    // 생성자 함수의 prototype 속성에 존재하는 jumps가 반영되어 있다.
    // 지정한 적이 없는 longEars는 undefined.
    console.log(rabbit1.jumps, rabbit1.longEars); // true undefined 
    
    // 확장 시도
    Rabbit.prototype.longEars = true;
    
    // 이미 인스턴스화 한 객체에 반영 된다.
    console.log(rabbit1.jumps, rabbit1.longEars); // true true
    
    // 확장 시도 이 후에 만들어진 인스턴스도 마찬가지다.
    var rabbit2 = new Rabbit();
    console.log(rabbit2.jumps, rabbit2.longEars); // true true
    
    // 두 인스턴스의 prototype은 같은 객체를 바라보고 있으며
    rabbit1.__proto__ === rabbit2.__proto__       // true
    
    // 그 객체는 생성자 함수의 prototype이다.
    rabbit1.__proto__ === Rabbit.prototype        // true
    
  • 프로토타입의 동작 방식은 '신비스러운’면이 있습니다. object에서 프로퍼티를 읽으려고 하는데 해당 프로퍼티가 없으면 자바스크립트는 자동으로 프로토타입에서 프로퍼티를 찾기 때문이죠. 프로그래밍에선 이런 동작 방식을 '프로토타입 상속’이라 부릅니다.

    https://ko.javascript.info/prototype-inheritance

    라네요.

    전에 만들어진 객체나, 새로 만들어진 객체나 같은 생성자 함수로 만든거면 프로토타입도 같을거고 그 다음은 위의 설명처럼 되겠죠?

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)