Why is it impossible to change constructor function from prototype?

前端 未结 3 507
别那么骄傲
别那么骄傲 2020-11-30 20:24

I have such example.

function Rabbit() {
    var jumps = \"yes\";
};
var rabbit = new Rabbit();
alert(rabbit.jumps);                    // undefined
alert(Ra         


        
相关标签:
3条回答
  • 2020-11-30 21:07

    Try the following

    function Rabbit() {
      this.jumps = "no";
    };
    
    var rabbit = new Rabbit();
    alert(rabbit.jumps);  // Prints "no"
    
    0 讨论(0)
  • 2020-11-30 21:16

    This is wonderful workaround creating an object from a literal, not from constructor function.

    Firstly, if you want jumps member to be contained in the object, rather than being just a local variable in the constructor then you need this keyword.

    function Rabbit() {
        this.jumps = "yes";
    };
    
    var rabbit = new Rabbit();
    alert(rabbit.jumps);                    // not undefined anymore
    

    And now you can easily now access jumps publicly the way you wanted:

    rabbit.jumps = 'no';
    alert(rabbit.jumps);                    // outputs 'no' 
    

    But still if you create another Rabbit object it will initially have 'yes' as defined in the constructor, right?

    var rabbit2 = new Rabbit();
    alert(rabbit.jumps);                     // outputs 'no' from before
    alert(rabbit2.jumps);                    // outputs 'yes'
    

    What you could do is creating a Rabbit from some default Rabbit Object. The concrete rabbits will always have the default value from default Rabbit object even when you change it on the fly unless you have changed the value in concrete rabbit object (implementation). This is a different than @Juan Mendes's solution which is probably the best but it can open another point of view.

    Rabbit = {jumps : 'yes'};    // default object
    
    rabbit = Object.create(Rabbit);
    Rabbit.jumps = 'no';
    rabbit2 = Object.create(Rabbit);
    
    console.log(rabbit.jumps);   // outputs "no" - from default object
    console.log(rabbit2.jumps);  // outputs "no" - from default object
    
    // but...
    rabbit.jumps = 'yes';
    Rabbit.jumps = 'unknown';
    
    console.log(rabbit.jumps);   // outputs "yes" - from concrete object
    console.log(rabbit2.jumps);  // outputs "unknown" - from default object
    
    0 讨论(0)
  • 2020-11-30 21:19

    You cannot change a constructor by reassigning to prototype.constructor

    What is happening is that Rabbit.prototype.constructor is a pointer to the original constructor (function Rabbit(){...}), so that users of the 'class' can detect the constructor from an instance. Therefore, when you try to do:

    Rabbit.prototype.constructor = function Rabbit() {
        this.jumps = "no";
    };
    

    You're only going to affect code that relies on prototype.constructor to dynamically instantiate objects from instances.

    When you call new X, the JS engine doesn't reference X.prototype.constructor, it uses the X as the constructor function and X.prototype as the newly created object's prototype., ignoring X.prototype.constructor.

    A good way to explain this is to implement the new operator ourselves. ( Crockford will be happy, no more new ;)

    // `new` emulator
    // 
    // Doesn't reference `.constructor` to show that prototype.constructor is not used
    // when istantiating objects a la `new`
    function make(ctorFun, argsArray) {
      // New instance attached to the prototype but the constructor
      // hasn't been called on it.
      const newInstance = Object.create(ctorFun.prototype);
      ctorFun.apply(newInstance, argsArray);
      return newInstance;
    }
    
    // If you create a utility function to create from instance, then it uses the
    // inherited `constructor` property and your change would affect that.
    function makeFromInstance(instance, argsArray) {
      return make(instance.constructor, argsArray);
    }
    
    function X(jumps) {
      this.jumps = jumps;
    }
    
    // Flip the constructor, see what it affects
    X.prototype.constructor = function(jumps) {
      this.jumps = !jumps;
    }
    
    const xFromConstructorIsGood = make(X, [true]);
    const xFromInstanceIsBad = makeFromInstance(xFromConstructorIsGood, [true]);
    
    console.log({
      xFromConstructorIsGood,
      xFromInstanceIsBad
    });

    Inheritance in JS

    Libraries that help with JS inheritance implement inheritance and do rely on prototype.constructor with something in the spirit the following:

    function extend(base, sub) {
    
      function surrogateCtor() {}
      // Copy the prototype from the base to setup inheritance
      surrogateCtor.prototype = base.prototype;
      sub.prototype = new surrogateCtor();
      // The constructor property is set to the base constructor
      // with the above trick, let's fix it
      sub.prototype.constructor = sub;
    }
    

    You can see that in the above code, we have to fix the constructor property because it's sometimes used to create instantiate an object when you only have an instance. but it doesn't affect the actual constructor. See my post about JS inheritance http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

    How to redefine a constructor If you really want to redefine a constructor, just do

    // If Rabbit had any custom properties on it 
    // (or static properties as some call it), they would not be copied, you'd have to do that manually using getOwnPropertyNames
    
    // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
    var oldProto = Rabbit.prototype;
    Rabbit = function() {...};
    Rabbit.prototype = oldProto;
    

    Note that this would not affect code that had already copied that reference, for example:

    const myRefRabbit = Rabbit
    
    0 讨论(0)
提交回复
热议问题