1

I have read every article and example on the first page of Google and I still am having a hard to completely understanding how to properly implement Prototypal Inheritance in JavaScript. The biggest challenge I'm facing is I am seeing many different ways to implement the inheritance.

First I will start with what I want to achieve if this was C#:

C#

class Base {
    public string UI { get; set; } // Using a string just for simplicity
}

class Book : Base {

    public Book(string title) {
        this.title = title;
    }
    private string title { get; set; } // Title of book
}

Then I can instantiate a Book and be able to access UI on each instance:

var myBook = new Book("East of Eden");
myBook.UI = "some string"; // This work.. all Book instances have THEIR OWN UI string

JavaScript

Now lets say I have a base object in JS that I want all other objects to inherit from:

function Base() {
    this.UI = {}
}

I then want another object type to inherit this model like this:

function Book(title){
    this.title = title;
}

Book.prototype = new Base();
// Sometimes I have seen this line instead... nothing seems to work at all when I use this though, so I don't understand whats happening here
//Book.prototype = Object.create(Base.prototype);
Book.prototype.constructor = Book;

Book.prototype.getTitle = function(){
    return this.title;
}

var myBook = new Book("East of Eden");
var anotherBook = new Book("Grapes of Wrath");

console.log(myBook.getTitle()); // East of Eden
myBook.UI.isRead = true;
console.log(myBook.UI);

console.log(anotherBook.getTitle()); // Grapes of Wrath
anotherBook.UI.isHardcopy = true;
myBook.UI.isRead = false;
console.log(anotherBook.UI); // This UI object has isRead on it as well!!! NOOOO

So this doesn't really work because both instances are sharing the same UI object, but what I want is for them to have their OWN instance of the UI object.

Another method I have seen is to not use the 'new' keyword at all and only use Object.create() to get new objects. However, I am not sure how I would implement my Base class with some subclass like Book and then create multiple instances of Book, each with their own UI properties.

Could someone please give me an example of how to inherit a base class and create instances of that subclass with their own UI objects? Thanks

EDIT

So would the "simple" way of achieve what I want just be to do something like:

var Base = {
    UI: {}
}

function Book(title){
  _.extend(this, Base);
  this.title = title;
}

var myBook = new Book("East of Eden");
myBook.UI.prop = 5; // This works now but doesn't utilize true inheritance at all!
Matt Hintzke
  • 7,744
  • 16
  • 55
  • 113
  • I can explain why this is happening, though I'm not sure about the best way to fix it. You set the prototype of a Book to a single (new) instance of Base, so all books have the same object as their prototypes. This is not the same "class" but literally the same in-memory object – Jason Bray Dec 18 '15 at 20:11
  • I think maybe one of the reasons why we are both confused (I also don't really get the 'how' part) is because if you don't want to share particular values between all objects of that 'type' then there's not much point in inheriting them. Since you can dynamically add the UI property whenever you want, why bother with inheriting a particular copy of the blank thing? If you wanted to copy some parts of UI and not others, you could set UI to have the prototype and new that up in the book constructor. Just some thoughts to try and help. – Jason Bray Dec 18 '15 at 20:24
  • if you want each instance to have it's own copy, use an own property (typically defined in the constructor), if you want it inherited, define it on prototype. – dandavis Dec 18 '15 at 20:34
  • The "inherited" properties are really like Static properties since all instances share the same memory space, correct? – Matt Hintzke Dec 18 '15 at 20:35
  • @MattHintzke: yes, but we typically refer to un-inherited properties on the constructor as "static": `String.fromCharCode(65)` vs `new String(123).bold()`; fromCharCode() is static, bold() is inherited. bold() is defined on `String.prototype`, fromCharCode() is defined on `String`. since prototype properties are shared, we usually only define methods there, since they can be re-used on many instances thanks to `this`. it's rare to see data on prototype, unless it's like an instance count, or something... – dandavis Dec 18 '15 at 20:37

2 Answers2

2

Prototypes are linked and not copied. This means that when you did:

function Base(){
    this.UI = {}
}

Book.prototype = new Base();
Book.prototype.constructor = Book;

The prototype of your Book constructor will be a new instance of Base. All your instances of Book will have the same prototype, the same instance of Base. Since it's this object which holds the UI property, all Book instances will fallback to the same object property.

Think that your Prototype will be:

var proto = {
   UI : { }
}

All your Book instances will have access to this object:

var a = new Book('East of Eden');
var b = new Book('Grapes of Wrath');

a.UI.prop = 'prop'; //proto.UI.prop === 'prop'
b.UI.prop === 'prop'; //because it's also proto.UI.prop

If you actually define a property on Book instances, say on its constructor:

function Book(title){
    this.title = title;
    this.UI = { };
}

You'll see that they are different objects:

a.UI !== b.UI //true
a.UI.prop = 'prop';
b.UI.prop !== b.UI; //true

Calling the constructor is the most obvious way to also initialize the properties on their children:

function Book(title){
     Base.call(this);
     this.title = title;
 }

Regarding the difference between new Base() and Object.create(Base.prototype).

new Base() will initialize the object and call the constructor, while Object.create(Base.prototype) will do basically the same, except it won't call the constructor. This means, that the prototype won't have the properties set on the constructor (UI).

MinusFour
  • 13,913
  • 3
  • 30
  • 39
  • So does this mean inheritance in JS doesn't result in the same behavior as it would in C# where all instances have their own UI object? How would I achieve the desired result in JS using inheritance? Is there not a way? – Matt Hintzke Dec 18 '15 at 20:31
  • No, they are different. Property must be defined on the object instance if you don't want it to fallback to the prototype object. – MinusFour Dec 18 '15 at 20:34
  • so would the "simple" way to achieve this just to use an extend method to "copy" over the base class values to the "sub-class" object? – Matt Hintzke Dec 18 '15 at 20:34
  • @MattHintzke: `Base.call(this)` inside Book() will define an own property `UI` on the Book instance. – dandavis Dec 18 '15 at 20:36
  • So I am mostly trying to set something up so I can make a ton of different models that all "inherit" a base object that has a `UI` object on it. Having to write `this.UI = {};` in every model seemed redundant and I thought there was a way to use inheritance to achieve this – Matt Hintzke Dec 18 '15 at 20:40
  • @dandavis oh really?? let me try that out – Matt Hintzke Dec 18 '15 at 20:41
  • You don't have to, you can call the constructor or any function that initialize properties on the object. – MinusFour Dec 18 '15 at 20:41
  • ES6 classes wireup a lot of these connections for you, it might be more recognizable to you (read: sane) to study ES6, then learn the diff between ES6 and ES5... – dandavis Dec 18 '15 at 20:43
  • Ahhh it was the `Base.call(this)` that I was missing. This calls the Base constructor using the Book `this` as the Base `this` and therefor adds the `UI` object to the Book `this`... makes total sense now. After doing that using both `Book.prototype = new Base();` and `Book.prototype = Object.create(Base.prototype)` work fine.. so what one is "correct"? – Matt Hintzke Dec 18 '15 at 20:45
  • Like I put at the end of the answer. `new` will actually call the constructor, initializing the property (you probably don't want it). `Object.create` won't call the constructor. The prototype of the created object is the same for both. – MinusFour Dec 18 '15 at 20:47
  • Ok so the fundemental difference is that using `new Base()` will add the UI object to the Book.prototype while the `Object.create(Base.prototype)` will not. – Matt Hintzke Dec 18 '15 at 21:21
1

The biggest challenge I'm facing is I am seeing many different ways to implement the inheritance.

There really is only one correct way.

Use Object.create to establish inheritance:

Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    configurable: true,
  }
});

Then apply the parent constructor in the child constructor to the child instance:

function Child() {
  Parent.call(this);
}

This is basically what happens under the hood if you'd use ES2015's new class syntax.

See Benefits of using `Object.create` for inheritance for more details on Object.create and why you want to use it in this case.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143