1

TL;DR? Why can't I overwrite a constructor's prototype from within the constructor?

I'm figuring out my pattern for prototypical inheritance. I don't like how prototypes are usually defined externally from a constructor, and want to logically encapsulate things better.

I found that the one magical line that I expected to work, doesn't.

function Orifice(){
  this.exhaust=function(){};
  this.ingest=function(){};
}
var standardOrifice = new Orifice();

function Sphincter(){
  this.constructor.prototype = standardOrifice; // <-- does not work
  this.relax=function(){};
  this.tighten=function(){};
}

Interestingly, I can write individual properties to this.constructor.prototype, but I cannot overwrite the whole prototype object the same way one can outside of a constructor's definition.

So stuff like this works:

  this.constructor.prototype.exhaust = standardOrifice.exhaust;
  this.constructor.prototype.ingest = standardOrifice.ingest;

For which I can create a simple clone function to handle this:

function extend(target){
  return {
    from: function(obj){
      target.__proto__ = obj.constructor.prototype;
      for (key in obj) if (obj.hasOwnProperty(key)) target[key]=obj[key];
      return target;
    }
  };
}

Thankfully in my tests so far, this technique appears to work well, though I'm not sure if there are details or performance cases I could be missing.

function Sphincter(){
  extend(this.constructor.prototype).from(standardOrifice);
  //...
}

Why can't I overwrite a constructor's prototype from within the constructor? Yet I can outside the constructor? And writing properties individually works from within a constructor?

ChaseMoskal
  • 7,151
  • 5
  • 37
  • 50
  • It kind of defies the benefits of prototype when you re set it on every instance creation – HMR Jan 23 '14 at 00:56

2 Answers2

5

Why can't I overwrite a constructor's prototype from within the constructor?

You can, but it's too late. The new instance has already been generated, inheriting from the old prototype. Maybe read how new works.

I don't like how prototypes are usually defined externally from a constructor.

That's just the way it is. You really should not setup the prototype from within the constructor - it would be executed everytime a new instance is created. That's specifically what prototypes are not supposed to be. See also Assigning prototype methods *inside* the constructor function - why not?

and want to logically encapsulate things better.

You might want to have a look at the various (revealing) module patterns. Or maybe even at some Class framework.

I'm currently looking for more concrete reasons that I should not go forth with the pattern I've been presenting.

It does not work in Internet Explorer. It would not work in any ES5-compliant environment that does not support the __proto__ property. You should never use it set a prototype on an existing object. Instead, use Object.create (or its shim) for Correct javascript inheritance - which requires that you overwrite the prototype outside of the constructor.

My suggestion is to call your extend helper outside the constructor on it, which still has a nice syntax.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Is there anything very wrong with extending the prototype within the constructor the way I have? Is it simply unconventional, or will it actually cause issues down the line? I understand that defining a prototype's functions in the constructor (for each instance) is a common mistake, but I don't think I'm doing that -- the shared functions are declared only one time. Perhaps there is a performance issue, for as much time as the extend() function takes? I'm guessing it can't be too bad; I think Resig's solution does a similar thing somewhere down the line. – ChaseMoskal Jan 22 '14 at 23:55
  • 1
    Unconventional: Yes. Causing issues: for example the ones you have experienced. Wrong: Yes, from the performance viewpoint - the prototype does only need to be setup only once (good that you're at least not re-creating the functions). Where do you see this in "Resig's solution" (which one?)? – Bergi Jan 22 '14 at 23:57
  • I accidentally submitted my comment early, trying to add a newline :) -- I edited to include my full comment. – ChaseMoskal Jan 22 '14 at 23:58
  • 1
    *Thanks to your words, I think I'm beginning to grasp.* Wholly overwriting `this.constructor.prototype` is actually setting the prototype that would be used for *the next instance*. Because our constructor is actually called after the instantiation of the object, it's already given the "default" prototype (or the prototype define by our last instance's extend() call, causing us to re-overwrite each extended property for each instance). ***Damn,*** *I wish you weren't so right.* It just looked so pretty... `` – ChaseMoskal Jan 23 '14 at 00:05
  • I added a line ([*in this paste*](https://pastee.org/hfsrf)) that makes the extend() function idempotent -- it now extends `this.constructor.prototype` only the first time. Hopefully that puts away the performance concern, but this doesn't necessarily stop the technique from being bad form in other ways I haven't yet foreseen. I will admit -- sexy syntax is a temptress. *I'm currently looking for more concrete reasons that I should not go forth with the pattern I've been presenting.* – ChaseMoskal Jan 23 '14 at 00:28
  • [Here](http://ejohn.org/blog/simple-javascript-inheritance/) is John Resig's inheritance technique -- the page demonstrates his `Class.extend` function, which at a glance, looks a lot like a much more sophisticated and complex version of what I'm doing with my little `extend()` -- leading me to believe (perhaps erroneously) that I'm on a track that hasn't derailed me quite yet :) – ChaseMoskal Jan 23 '14 at 00:30
  • I've also seen that it's unfavorable to wholly overwrite a constructor's prototype when it's declared externally as well -- I've had it explained to me, that `function A(){} A.prototype={dog:true};` actually eliminates some built-in prototype properties that are supposed to be present, making one have to do some goofy seemingly-redundant hogwash like `A.prototype.constructor=A;`. Since I'm not even supposed to wholly overwrite the prototype externally, is it safe to assume that doing so internally is equally strange, which is why it doesn't work? *I need some Brandy.* – ChaseMoskal Jan 23 '14 at 00:36
  • I'm not concerned about that little loop (and not about constructor performance at all [until it matters](https://en.wikipedia.org/wiki/Premature_optimization)), but [from an idealistic, perfectionistic view] with the *call* to `extend` itself :-) Also notice that Resig's script ([updated version](http://stackoverflow.com/a/15052240/1048572)) does create a complete new constructor (the new "class"), and never changes anything on it thereafter. – Bergi Jan 23 '14 at 00:37
  • So, this *basically* comes down to personal syntactical ideals, right? I think I totally get where you're coming from. I'm basically creating my own hoop to jump through, just because I think it looks prettier, where other developers just see that I've created a loop for no concrete reason, which in itself is terribly ugly. So then, it's up to me whether or not it's worthwhile to jump that hoop for my own personal pleasure, at the expense of the confusion and utter disapproval of my fellow developers. *What a toss-up :D* Thanks a million Bergi, I greatly appreciate it. – ChaseMoskal Jan 23 '14 at 00:48
  • Hah, newline thing again! Gets me nearly every time! – ChaseMoskal Jan 23 '14 at 00:48
  • Maybe it's personal, but afaik the majority of devs thinks like that :-) There are however real problems to consider, see my edit. My personal opion is that can jump through your hoop as long as performance does not matter (it hardly will ever), but you should be warned that you might need to build a completely different hoop when problems arise (with major code refactoring). – Bergi Jan 23 '14 at 00:58
  • After some time soaking in this knowledge, I have compromised and created a pattern that follows your answer's final suggestion. I'm hoping you'll agree that [***this new pattern*** (pastee.org)](https://pastee.org/92wbc) is the way that I should be doing things. Basically, I use a helper extend function to specify the prototype prior to the constructor's definition like so: `var Sphincter = from(standardOrifice).extend(function Sphincter(){ /*..*/ });` – ChaseMoskal Jan 23 '14 at 02:44
  • Probably going to flip it to `extend(parent).to(Child)`. – ChaseMoskal Jan 23 '14 at 02:52
  • Looks good :-) You can easily enable prototypical inheritance in that pattern without modifying the usage code. For the current code, you might consider the name `mix(standardOrifice).into(…)` :-) – Bergi Jan 23 '14 at 02:58
  • I really love the look of `mix(a).into(B)` -- I sincerely appreciate your guidance today – ChaseMoskal Jan 23 '14 at 03:20
1

Answer to the Specific Question

Why can't I overwrite a constructor's prototype from within the constructor?

It's because constructors are actually called after your object has already been instantiated. And since your object has managed to instantiate before your constructor has touched anything, your constructor has also already been assigned a "default" prototype.

Adding properties to this.constructor.prototype seems to work -- because you're actually manipulating the constructor's pre-assigned default prototype object, which all of your instances inherit from.

In my examples, this.constructor.prototype ended up referring to the default-assigned prototype of the constructor: so wholly overwriting it meant all new instances from that moment onward would have that new prototype -- as Bergi said, "too late" -- your current instance would not have that new prototype, as it still has the old default-assigned prototype because it's already been instantiated.

A Better Pattern for Avoiding Nonsense

I've come to understand, that the techniques presented in my question simply won't do. The question itself is generally misguided. By combining Bergi's wisdom with my own personal biases, I've come up with this pattern as a means to avoid having to find an answer to the original question altogether:

function extend(p){
  return { to: function(C){ for (k in p) if (p.hasOwnProperty(k)) 
  C.prototype[k]=p[k]; return C; } };
};

var orifice = new function Orifice(){
  this.exhaust=function(){};
  this.ingest=function(){};
};

var Sphincter = extend(orifice).to(function Sphincter(){
  this.relax=function(){};
  this.tighten=function(){};
});


Here's the extend function, expanded:

function extend(parentObject){
  return { 
    to: function(ChildConstructor){
      for (key in parentObject) 
        if (parentObject.hasOwnProperty(key)) 
          ChildConstructor.prototype[key] = parentObject[key];
      return ChildConstructor;
    }
  };
};

I used this to test that it works:

// TESTING
var s=new Sphincter();
var tests=['relax','tighten','exhaust','ingest'];
for (var i in tests) console.log("s."+tests[i]+"() is "+(tests[i]in s?"present :)":"MISSING!"));
ChaseMoskal
  • 7,151
  • 5
  • 37
  • 50