3

I want to add a method to the prototype of an inner class. Is that possible?

This is what I tried:

var Foo = function Foo() {};
Foo.prototype = {

    Bar: function Bar() { },
    Bar.prototype: { // <- this does not work

        barMethod: function () {
            console.log('hello');
        }

    },

    fooMethod: function () {
        var bar = new this.Bar();
        bar.barMethod();
    }

}

var foo = new Foo();
foo.fooMethod();

But Bar.prototype doesn't work, it throws a SyntaxError with the message Unexpected token ..

x-ray
  • 3,279
  • 5
  • 24
  • 37
  • Why do you use "inner classes"? And why do you store them on the prototype? – Bergi Jun 20 '15 at 14:21
  • 5
    You will need to follow the normal rules for object literals. You cannot have "nested property names". Use an assignment instead. – Bergi Jun 20 '15 at 14:22
  • 1
    Minor nit, JS doesn't have classes. On one level ES6 does, but it's syntactic sugar. – Dave Newton Jun 20 '15 at 14:54
  • Alright. :) I should have said something like "I want to add a method to a nested prototype" or something like that. – x-ray Jun 20 '15 at 15:13

2 Answers2

3

Your problem is that you're defining the Foo.prototype object, and as such, you're bound to the rules and syntax of object creation, for example the parser expects you to list the attributes in a key: value, nextkey: nextvalue format, which Bar.prototype does not fit. (Keys are effectively strings, and it doesn't make sense to use . in them, as they would create ambiguity while parsing*)

Try this:

var Foo = function Foo() {};
Foo.prototype.Bar = function Bar() { };
Foo.prototype.Bar.prototype = {

    barMethod: function () {
        console.log('hello');
    }

};

There's a slight semantic difference though, as this way you're not overriding the prototype, just extending it. (consider equaling it to {} first, then extending it with every attribute of the object you tried to create)


(*) A note on ambiguity: I mention above that having a . in your object key would create ambiguity, here's a simple example:

var foo = {
   bar: {
       baz: 0,
       qux: 20
   },
   bar.baz: 150 //note that this will throw an error
};
console.log(foo.bar.baz);

If this code above wouldn't throw an error, what would you expect console.log(foo.bar.baz) to print, 0 or 150?

That's why it doesn't make sense to use . in a key, and that's why the parser throws the unexpected token error on any ..

Of course, you could use the "bar.baz" string as a key as "bar.baz": 150 above (please don't!), but then you'd have to reference the value as

foo["bar.baz"]

which would be distinctly different from

foo.bar.baz;

All in all, this is just some intuition-based reasoning behind why you can't use a . in your keys, but the real reason is plainly this: because the parser will throw an error.

doldt
  • 4,466
  • 3
  • 21
  • 36
  • Appear to return `TypeError: Cannot set property 'prototype' of undefined` ? – guest271314 Jun 20 '15 at 14:38
  • Well first you have to create Foo (the way you originally did), of course, I omitted that line. – doldt Jun 20 '15 at 14:39
  • Alright, this works the way I want. But where exactly is the semantic difference? Where would you add `= {};`? – x-ray Jun 20 '15 at 14:42
  • You dont need to add it in this case. I was just pointing out that if you're doing `f.prototype= {...}` then you're redefinind the prototype attribute, while `f.prototype.bar = ...` is just adding `bar` to whatever the prototype attribute was before. – doldt Jun 20 '15 at 14:46
  • I understand that `Bar.prototype:` is simply not supported. But why would a `.` in a string create ambiguity while parsing? – x-ray Jun 20 '15 at 14:48
  • @x-ray: See [here](https://stackoverflow.com/questions/17474390/defining-a-javascript-prototype) about the difference. In fact, you should do the same for `….Bar.prototype.barMethod`. – Bergi Jun 20 '15 at 14:58
  • @Bergi I know about the difference between overriding and extending the prototype. But I wondered about the "slight semantic difference" doldt mentioned, because his example overrides Bar's prototype just as I attempted to. – x-ray Jun 20 '15 at 15:05
  • @x-ray I added an unnecessailly long note on the ambiguity I meant :) That "slight semantic difference" I mentioned is the difference between overriding and extending, nothing else. – doldt Jun 20 '15 at 15:44
1

In an object literal you can only define properties of that object literal, but not properties of the values.

However, if you want to set the prototype when creating the object, consider Object.assign (which can be polyfilled):

Foo.prototype = {
  Bar: Object.assign(function Bar() { }, {
    prototype: {
      barMethod: function () {
        console.log('hello');
      }
    }
  }),
  fooMethod: function () {
    var bar = new this.Bar();
    bar.barMethod();
  }
};

However, note that replacing all the prototype is a bad practice because you erase the constructor property.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • I should have mentioned I use node.js (v0.12.4), which doesn't have `Object.assign` available. There is a shim available (https://www.npmjs.com/package/object.assign), but I haven't tested it. – x-ray Jun 20 '15 at 15:24
  • Oriol, in this case, he was trying to replace the prototype *property* of a function (not to be confused with the prototype of the function that getPrototypeOf would return) with a new object, meaning that new objects created with this function would still have the constructor property, as their prototype would be Object. – doldt Jun 20 '15 at 15:49
  • @doldt Yes. In my answer I only talked about `prototype`, not `[[Prototype]]`. – Oriol Jun 20 '15 at 16:33