Not traversing the prototype chain?

Started by
9 comments, last by tanzanite7 10 years, 3 months ago
I've come to the conclusion I missed something very important about the prototype chain and how to set up "proper" OOP inheritance in Javascript.
Here's what I think I'm doing:

01function Interesting() {
02    this.a = []; /* Array of Auxiliary structures */;
03    this.b = ...;
04}
05Interesting.prototype = {
06    Auxiliary: function(c, d, e, f) {
07        /* validity checks.
08        This is a basic "packet" of data.
09        It could be a stand-alone object but since it's used only
10        when dealing with Interesting objects, I decided to define
11        its ctor here. */
12    }
13};
14// ...
15Interesting.prototype.Auxiliary.prototype = {
16    c: undefined, // I'm still not sure about the style a base class should have BTW
17    d: undefined,
18    e: undefined,
19    f: undefined
20};
21// ...
22function DefaultInteresting() {
23    Interesting.call(this); // Seems to work ok.
24    this.parts[0] = new Interesting.Auxiliary(1, 2, 3, 4); // Auxiliary is undefined
25    this.b[0] = undefined;
26};
27DefaultInteresting.prototype = Interesting; // new Interesting() does not seem to work either

Problem is line 24. If I do new Interesting.prototype.Auxiliary(1, 2, 3, 4) then it calls the correct function.

Why is this happening? It was my understanding this should have been automatic. The inheritance chain in the debug window appears to be indeed correct. An hour of googling made me more confused.
What do you suggest me to look at?

Previously "Krohm"

Advertisement
I have not slept in the last 28 hours, so ... i am bit dopy and not particularly helpful (and probably say some gibberish), but ...

I would throw away the "New" stuff and specify the prototype chain yourself (the "new" junk i think was added to the language to look more familiar for people coming from other programming languages - it's kinda hack. Having its own shadowy prototype chain which at best confused the bejesus out of anyone).

Some example:
var BaseObj = Object.create(null)  // new object with null as its prototype - clean object, no crap attached.
BaseObj.defaultA = 1 // some default value for BaseObj - everything will see it through the prototype chain.
BaseObj.defaultB = 42 // or you could treat it as a static variable by accessing it directly via BaseObj.defaultB instead of this.defaultB where "this" is always whatever object is bound ;)
BaseObj.build = function(a) { // no constructor for this object as it is just a base class
  this.a = a // will change (or add when it was missing) "a" to the bound "this" object (!!!)
  // ... which should never be BaseObj, unless you called BaseObj.build(123) in which case it obviously is exactly that
  // ... but you probably do not want (unless that "a" was supposed to be a "static" variable - to use the parlance from other languages)
  alert(this.defaultB) // obj = FunObj.make() will alert "2": obj does not have it defined, but FunObj in its prototype chain has - masking BaseObj
  // this.defaultB = 77 // this would not change BaseObj.defaultB, but add "defaultB" to "obj" (or whatever "this" is bound to)
}

var FunObj = Object.create(BaseObj) // new object with its prototype set to BaseObject
FunObj.defaultB = 2
FunObj.build = function(a, defA) {
  BaseObj.build.bind(this)(a) // FunObj has a build and will be found before
  // ... lets skip that and call parent builder explicitly .. a'la: "super(a)"
  this.defaultA = defA
}
FunObj.make = function() { // out custom constructor
  var obj = Object.create(FunObj)
  FunObj.build.bind(obj)(11, 22) // we want "this" to be "obj" while calling FunObj.build(11, 22)
  return obj
}

var snafu = FunObj.make()
// snafu.defaultA == 22
// snafu.defaultB == 2
// snafu.a = 11
// BaseObj and FunObj are unchanged, ie. BaseObj.defaultA == 1, BaseObj.defaultB == 42 etc
Much easier to understand what is going on. Ie. you really, really need to understand the, very-very simple, prototype style inheritance anyway to get anywhere with javascript - "new" is just a unneeded obstacle smudging the water for no benefit (well, at least i have not found one).

(hopefully i managed to be somewhat intelligible)
2 edits later:
(i need sleep, bye)

I don't know, it seems to me it's not very much cleaner. It was my understanding that Object.create was supposed to be used to instance prototypal objects only.

Previously "Krohm"

"prototypal objects only"

???

That is all JavaScript has - there is nothing else really, just some wrapper syntax in the form of "new" / ".prototype" to confuse the matter.

Let's examine what "new" actually does:

function Bar() {}
function Foo() {}
// window.Foo is now an object of type function, its prototype is Function. Also, let's call the actual prototype of any object: "__proto__" to avoid confusion from now on.
// window.Foo.prototype == generic base object (constructor: "Function", __proto__: Object)
// IMPORTANT: "prototype" property here has nothing todo with an objects actual prototype (__proto__) ... lovely.
// "prototype" is kind of poor mans constructor.
 
Bar.prototype = new Foo;
// This line is equivalent to:
//   Bar.prototype = Object.create(Foo.prototype)
//   Foo.bind(Bar.prototype)()
That is all the magic "new"/"prototype" adds. Seriously. That is all it does.

A bit less typing (arguably) and a "prototype" property that has nothing todo with prototype (aka: __proto__). Forces you to inherit lots of unnecessary crap from the generic base object it uses. Is extremely limited on what one can do (the function object [a'la: Foo, Bar] serves as an FIXED SINGLE constructor). And in the end, none of it resembles any class based language (like Java etc) - ie, one still has to understand how the prototype chain works and program constructors etc accordingly - no help there from "new". Under the "new" wrapper - JavaScript is still JavaScript.

My suggestion: first understand how JavaScript actually works (ie. ignoring "new" and "prototype" property completely, __proto__ all the way) - then delve into "new", if at that point you see any point in doing so.
Might as well comment on the OP now that my previous posts have covered the basics, somewhat.

Problem is line 24. If I do new Interesting.prototype.Auxiliary(1, 2, 3, 4) then it calls the correct function.

Why is this happening? It was my understanding this should have been automatic. The inheritance chain in the debug window appears to be indeed correct. An hour of googling made me more confused.
What do you suggest me to look at?

Your error is in thinking that "prototype" property has anything todo with prototype X_X *insert-rolleyes-etc*

"prototype" property is used behind the scenes to link up the real prototype chain via "new" as shown in previous post.

Btw, the correct way would be:
function Interesting ...
function Auxiliary ...
Auxiliary.prototype = new Interesting // (*)

obj = new Auxiliary // now obj.__proto__ == Auxiliary and obj.__proto__.__proto__ == Interesting

Which is not terribly useful, to say the least. Mostly because of how one is expected to use "constructors", see (*). The constructor is not a constructor for Instance - it is, in a sense, for Type! You need to construct the instance separately (logically, not necessarily in code ... if you can stand the headaches it will cause down the road) - a'la "build" in my first post.

I am unable to make any sense out of what you're writing. BTW, what you write seems to collide against what MDN states in multiple documents.

Previously "Krohm"

Then you are either misinterpreting what MDN or I say (or both). Care to elaborate? Maybe i can identify where the confusion lies.

This is fairly good roundup:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model

Highly "new" oriented, but sufficiently covers what i said (*), albeit with a lot of fluff - a hell of a lot of fluff. Have encountered a much more succinct roundup - but my Google-fu fails me atm.

(*) ie. what exactly "new" does, "prototype" vs "__proto__", how prototype chain works, inheritance pitfails.

Note that in the article you mention (which is not new to me), when defining Manager extending Employee, they write


Manager.prototype = new Employee;

I don't think they are doing that because .prototype is irrelevant and in "determining instance relationships", the correlation between those two variables is further elaborated. While __proto__ appears in widespread use, I think I've read somewhere it was never meant to be exposed for real. Its doc page states indeed its use is deprecated.

It is worth noticing however they build the inheritance chain as


DefaultInteresting.prototype = new Interesting;

And not as I am doing, neither new Interesting() nor Interesting. This really makes sense on its own.

Previously "Krohm"

I don't think they are doing that because .prototype is irrelevant

If it would be irrelevant then it would not exist at all - so, obviously it is used for something. It's only use is for "new" (as described earlier and in that article) - it has no other uses (well, exceptions excepted: instanceof vs isprototype). On its own - it does nothing (inc. not used for traversing prototype chain when reading object properties => ie. not the real prototype chain).

While __proto__ appears in widespread use, I think I've read somewhere it was never meant to be exposed for real. Its doc page states indeed its use is deprecated.

It indeed was not meant to be exposed - however, except IE they all do using "__proto__". Also, in future versions (ES6) it will be exposed as "__proto__" (not sure if the specification is finalized yet).

It is a more convenient alternative for the already standardized (hence the deprecation note) Object.setPrototypeOf/getPrototypeOf.

Its exposedness is not relevant to its existence tho (it is a fundamental part of the language). Referring to the property holding the real prototype chain using "__proto__" is just convenient (as virtually everything except IE exposes it with that name already).

"__proto__" = prototype chain
"prototype" = used to build the prototype chain for new objects created by "new"

... just got to love the confusion "new" managed to shoehorn into the language.

It is worth noticing however they build the inheritance chain as

DefaultInteresting.prototype = new Interesting;
And not as I am doing, neither new Interesting() nor Interesting. This really makes sense on its own.

Glad you are making progress on how to use "new" and its way of building the prototype chain. Just be sure you understand also what is going on under the hood.

Worth repeating in that regard perhaps:

1) Foo.prototype = SomeObject
2) Bar.prototype = new Foo
3) obj = new Bar

line 2: creates a new object and sets its "__proto__" to "SomeObject" and stores it in "Bar.prototype" (not in "Bar"'s prototype chain which is in "__proto__")

line 3: similarly creates a new object and sets its "__proto__" to the object stored in "Bar.prototype" (previously, line 2 stored an empty object with its "__proto__" set to "SomeObject" in there).

Ie. objects prototype, used by "new", is in "prototype" - with its real prototype chain pre built by line 2 and similar previous constructs.

Note that the property name "prototype" as an objects "prototype" makes sense - just that it is not the objects prototype CHAIN.

edit: well, just remembered that "instanceof" is related to "new" - can't think of anything else.

I still don't understand the point you're trying to make here.

I suspect something is missing in translation.

The more I think at your "prototype is redundant" argument however, the more I think you are taking it the other way around.

Starting point: .__proto__ does not formally exist.

Inference: using .__proto__ is non-conforming.

Note: new is to be used to move value .prototype to implementation-specific management - ideally implementation-private data.

This still does not come to provide any hint on why using it does not influence the object prototype chain, as its value is copied at construction time, coherently with MDN documentation. The only thing you say is that .prototype does not take part not object's prototype chain. This is incorrect, as you write yourself above. A more correct statement would be that current .prototype value is not considered during object prototype chain traversal, but this is irrelevant to my problem, which I suspect to be lingering in subtle line order issues.

As a side note, MDN isn't deprecating Object.setPrototypeOf(...) as you seem to suggest above - it marks .__proto__ as deprecated, coherently with a long history of documentation.

Last but not least, standardizing .__proto__ is just a terrible idea but of course nobody cares about that - see how let is supported!

Previously "Krohm"

This topic is closed to new replies.

Advertisement