36

I typically implement inheritance along the following lines.

function Animal () { this.x = 0; this.y = 0;}

Animal.prototype.locate = function() { 
  console.log(this.x, this.y);
  return this;
};
Animal.prototype.move = function(x, y) {
  this.x = this.x + x;
  this.y = this.y + y; 
  return this;
}


function Duck () {
    Animal.call(this);
}

Duck.prototype = new Animal();
Duck.prototype.constructor = Duck;
Duck.prototype.speak = function () {
    console.log("quack");
    return this;
}

var daffy = new Duck();

daffy.move(6, 7).locate().speak();

I've read this post by Eric Elliott and if I understand correctly I can use Object.create and Object.assign instead? Is it really that simple?

var animal = {
   x : 0,
   y : 0,
   locate : function () { 
     console.log(this.x, this.y);
     return this;
   },
   move : function (x, y) { 
     this.x = this.x + x; 
     this.y = this.y + y;
     return this;
   }
}

var duck = function () {
   return Object.assign(Object.create(animal), {
     speak : function () { 
       console.log("quack");
       return this;
     }
   });
}

var daffy = duck();

daffy.move(6, 7).locate().speak();

As an aside, by convention constructor functions are capitalized, should object literals that act as constructors also be capitalized?

I realise there are many questions here discussing new versus Object.create, but they typically seem to relate to Duck.prototype = new Animal(); versus Duck.prototype = Object.create(Animal.prototype);

9
  • 2
    If you're using ES2015, you should be using class & extend Commented Nov 13, 2015 at 12:39
  • 19
    @Amit that's obviously not the question he asked. Whether using class and extend is a good idea is part of another discussion. Commented Nov 13, 2015 at 12:46
  • 2
    @Amit -Why should I use class and extend? If I do that I end up having to use new don't I? What advantage does using class and extend have over Object.assign(Object.create(...? Commented Nov 13, 2015 at 12:46
  • 3
    I added the ES6 tag back into this since the main question is can I use Object.create and Object.assign. Commented Nov 13, 2015 at 14:50
  • 1
    Following that pattern you would rather have var duck = Object.assign(Object.create and var daffy = Object.create(duck). Commented Nov 13, 2015 at 17:38

3 Answers 3

34

Yes, it is that simple. In your example with Object.create/Object.assign, you are using a factory function to create new instances of duck (similar to the way jQuery creates new instances if you select an element with var body = $('body')). An advantage of this code style is, that it doesn't force you to call a constructor of animal when you want to create a new duck instance (as opposed to ES2015 Classes).

Differences in initialization

Maybe one interesting tidbit that works slightly differently than if you were to use a constructor (or any other initialization function):

When you create a duck instace, all the properties of animal are in the [[Prototype]] slot of the duck instance.

var daffy = duck();
console.log(daffy); // Object { speak: function() }

So daffy does not have any own x and y properties yet. However, when you call the following, they will be added:

daffy.move(6, 7);
console.log(daffy); // Object { speak: function(), x: 6, y: 7 }

Why? In the function-body of animal.move, we have the following statement:

this.x = this.x + x; 

So when you call this with daffy.move, this refers to daffy. So it will try to assign this.x + x to this.x. Since this.x is not yet defined, the [[Prototype]] chain of daffy is traversed down to animal, where animal.x is defined.

Thus in the first call, the this.x on the right side of the assignment refers to animal.x, because daffy.x is not defined. The second time daffy.move(1,2) is called, this.x on the right side will be daffy.x.

Alternative Syntax

Alternatively, you could also use Object.setPrototypeOf instead of Object.create/Object.assign (OLOO Style):

var duck = function () {
   var duckObject = {
       speak : function () { 
           console.log("quack");
           return this;
       }
   };
   return Object.setPrototypeOf(duckObject, animal);
}

Naming Conventions

I'm not aware of any established conventions. Kyle Simpson uses uppercase letters in OLOO, Eric Elliot seems to use lowercase. Personally I would stick with lower-case, because the object literals that act as constructors are already fully fledged objects themselves (not just blueprint, like classes would be).

Singleton

If you only wanted a single instance (e.g. for a singleton), you could just call it directly:

var duck = Object.assign(Object.create(animal), {
    speak : function () { 
        console.log("quack");
        return this;
    }
});

duck.move(6, 7).locate().speak();
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the long answer. I suppose an advantage ofObject.assign(Object.create over setPrototypeOf is that I can do more easiliy do "multiple inheritance" (for want of a better term) e.g Object.assign(Object.create(animal), duckObj, someOtherObj, anotherObj)? Are there any disadvantages to Object.assign(Object.create?
Object.assign(Object.create(animal), duckObj, someOtherObj, anotherObj) is more of a mixin mixed with prototypes :) The resulting object would contain the own properties of duckObj, someOtherObj and anotherObj and would only have animale in the [[Prototype]] slot.
If you want to have multiple objects in the prototype chain, you would have to mix them together in Object.create: Object.assign(Object.create({animal: animal, bird: bird}), duck); or Object.setPrototypeOf(duck, {animal: animal, bird: bird});
I'm not aware of any downsides of using Object.assign/Object.create, except for a small one that probably wont matter in most cases: Objects created using Object.create (as opposed to new) don't profit from hiddenClass support in browsers: medium.com/javascript-scene/…
This comment If you want to have multiple objects in the prototype chain, made me wonder how I would instantiate daffy if I wanted animal.isPrototypeOf(daffy) and bird.isPrototypeOf(daffy) to both return true?
|
13

I've read this post by Eric Elliott and if I understand correctly I can use Object.create and Object.assign instead? Is it really that simple?

Yes, create and assign is much more simple because they're primitives, and less magic is going on - everything you do is explicit.

However, Eric's mouse example is a bit confusing, as he leaves out one step, and mixes the inheritance of mouses from animals with instantiating mouses.

Rather let's try transcribing your duckling example again - let's start with doing it literally:

const animal = {
  constructor() {
    this.x = 0;
    this.y = 0;
    return this;
  },
  locate() { 
    console.log(this.x, this.y);
    return this;
  },
  move(x, y) {
    this.x += x;
    this.y += y; 
    return this;
  }
};
const duck = Object.assign(Object.create(animal), {
  constructor() {
    return animal.constructor.call(this);
  },
  speak() {
    console.log("quack");
    return this;
  }
});
/* alternatively: 
const duck = Object.setPrototypeOf({
  constructor() {
    return super.constructor(); // super doesn't work with `Object.assign`
  },
  speak() { … }
}, animal); */

let daffy = Object.create(duck).constructor();
daffy.move(6, 7).locate().speak();

You should see that what happens here is really no different from using constructors (or class syntax for that matter), we've just stored our prototypes directly in the variables and we're doing instantiation with an explicit call to create and constructor.

Now you can figure that our duck.constructor does nothing but calling its super method, so we can actually omit it completely and let inheritance do its work:

const duck = Object.assign(Object.create(animal), {
  speak() {
    console.log("quack");
    return this;
  }
});

The other thing that is often changed is the initialisation of instance properties. There is actually no reason to initialise them if we don't really need them, it's sufficient to put some default values on the prototype:

const animal = {
  x: 0,
  y: 0,
  locate() { 
    console.log(this.x, this.y);
  }
};
const duck = … Object.create(animal) …;

let daffy = Object.create(duck); // no constructor call any more!
daffy.x = 5; // instance initialisation by explicit assignment
daffy.locate();

The problem with this is that it only works for primitive values, and it gets repetitive. This is where factory functions get in:

function makeDuck(x, y) {
    return Object.assign(Object.create(duck), {x, y});
}
let daffy = makeDuck(5, 0);

To allow for easy inheritance, the initialisation is often not done in the factory but in a dedicated method so it can be called on "subclass" instances as well. You may call this method init, or you may call it constructor like I did above, it's basically the same.

As an aside, by convention constructor functions are capitalized, should object literals that act as constructors also be capitalized?

If you're not using any constructors, you may assign a new meaning to capitalized variable names, yes. It might however be confusing for everyone who's not accustomed to this. And btw, they're not "object literals that act as constructors", they're just prototype objects.

2 Comments

"I've read this post by Eric Elliott" is where you stop and disregard everything. No person knows less about good design than this loudmouth simpleton. Everything can be done with 'new' and 'call' and 'object.create' -- the last of which only for inheritance. The above example is so overwritten as to be singular proof of the Kruger Dunning effect. Thankfully ES2015 has rationally added extend and class keywords.
@Hal50000 Instead of calling anyone a "loudmouth simpleton", you might want to argue why class, extends and new are a "better design". I'd even argue that they are exactly the same design as the above example, only with more magic syntactic sugar. So what part is "overwritten"?
0

Rather than "inheritance" you should think about what type of "instantiation pattern" you intend to use. They have different purposes for implementation.

The top example is prototypal and the bottom, functional-shared. Check out this link: JS Instantiation patterns

Also, this is non-es6 related.

8 Comments

This does not cover his example. And yes, Object.assign is ES6.
Not sure why you say "Also, this is non-es6 related." Object.assignis part of the ES6 standard.
ES6 as new standards for what you're essentially using as classes. The code posted is specifically related to ES5.
@nils, if you check out the link in my initial response you'll see what I'm talking about.
No, it is not specifically related to ES5 (see the two comments above, Object.assign does not exist in ES5). And the Object.create/Object.assign combo is a perfectly valid pattern in ES6. And as stated above, your link does not pertain to the Object.create/Object.assign case, it only talks about Object.create in conjuncture with x.prototype. This is specifically what the OP is NOT asking about.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.