Benefits of Using 'Object.Create' For Inheritance

Benefits of using `Object.create` for inheritance

In the following I assume you are only interested in why Object.create is preferable for setting up inheritance.

To understand the benefits, lets first clarify what a "class" is made of in JavaScript. You have two parts:

  1. The constructor function. This function contains all the logic to create an instance of the "class", i.e. instance specific code.

  2. The prototype object. This is the object the instance inherits from. It contains all methods (and other properties) that should be shared among all instances.

Inheritance establishes an is-a relation, for example, a Dog is an Animal. How is this expressed in terms of constructor function and prototype object?

Obviously a dog must have the same methods as an animal, that is the Dog prototype object must somehow incorporate the methods from the Animal prototype object. There are multiple ways to do this. You will often see this:

Dog.prototype = new Animal();

This works because an Animal instance inherits from the Animal prototype object. But it also implies that every dog inherits from one specific Animal instance. That seems to be a bit strange. Shouldn't instance specific code only be run in the constructor function? Suddenly instance specific code and prototype methods seem to be mixed.

We don't actually want to run Animal instance specific code at that moment, we only want all the methods from the Animal prototype object. That is what Object.create lets us do:

Dog.prototype = Object.create(Animal.prototype);

Here we are not creating a new Animal instance, we only get the prototype methods. The instance specific code is executed exactly where it should be, inside the constructor:

function Dog() { 
Animal.call(this, 'Dog');
}

The biggest advantage is that Object.create will always work. Using new Animal() only works if the constructor does not expect any arguments. Imagine if the constructor looked like this:

function Animal(name) { 
this.name = name.toLowerCase();
}

You always have to pass a string to Animal, otherwise you will get an error. What will you pass when you do Dog.prototype = new Animal(??);? It doesn't actually matter which string you pass, as long as pass something, which hopefully shows you that this is bad design.


Some say that Dog.prototype = Animal.prototype; can also work. So now I'm totally confused

Everything that "adds" the properties from Animal.prototype to Dog.prototype will "work". But the solutions are of different quality. In this case here you will have the problem that any method you add to Dog.prototype will also be added to Animal.prototype.

Example:

Dog.prototype.bark = function() {
alert('bark');
};

Since Dog.prototype === Animal.prototype, all Animal instances have a method bark now, which is certainly not what you want.

Object.create (and even new Animal) add one level of indirection to the inheritance by creating a new object which inherits from Animal.prototype and that new object becomes Dog.prototype.


Inheritance in ES6

ES6 introduces a new syntax to create constructor functions and prototype methods, which looks like this:

class Dog extends Animal {

bark() {
alert('bark');
}

}

This is more convenient than what I explained above, but as it turns out, extends also uses an internal equivalent to Object.create to setup inheritance. See steps 2 and 3 in the ES6 draft.

Which means that using Object.create(SuperClass.prototype) is the "more correct" approach in ES5.

What is a practical advantage of Object.create over Constructor function?

Calling a constructor function:

 const child = new Human();

is (nearly) the same as:

 const child = Object.create(Human.prototype);
Human.call(child);

therefore I would not see Object.create as a language feature, but rather as a way to understand prototypal inheritance in JS.

There are very very limited usecases for prototype chains without constructors. One example would be the deserialization of a Human:

 const serialized = JSON.stringify(child); // Human inheritance gets lost, its a plain object now

const child2 = Object.assign(Object.create(Human.prototype), JSON.parse(serialized));

JavaScript inheritance using Object.create vs using new

You need to call the parent constructor to get the parent part of the instance initialized:

var Circle = function () {
Shape.call(this);
};

Circle.prototype = Object.create(Shape.prototype);
// plus the `constructor` property, but that’s not too important here

Assigning new Shape() to Circle.prototype is wrong, as you’ve seen – instances don’t make good prototypes.

Using Object.create for inheritance?

  • Object.create(Shape) returns an object which inherits from Shape.

    If you want to make a subclass of Shape, you probably don't want to do this.

  • Object.create(Shape.prototype) returns an object which inherits from Shape.prototype.

    Therefore, this object will not have the x and y own properties.

  • new Shape() does this:

    1. Creates an object which inherits from Shape.prototype.
    2. Calls Shape, passing the previous object as this.
    3. Returns that object (assuming Shape didn't return another object).

    Therefore, this object will have the x and y own properties.

Advantage of using Object.create

The speed difference is not very noticeable, since by nature you probably won't be creating too many objects (hundreds, even thousands isn't what I call a lot), and if you are and speed is a critical issue you probably won't be coding in JS, and if both of the above aren't true, then I'm sure within a few releases of all popular JS engines the difference will be negligible (this is already the case in some).

In answer to your question, the reasons aren't speed-related, but because the design pattern of Object.create is favoured to the old method (for the reasons outlined in that and other answers). They allow for proper utilisation of the ES5 property attributes (which make for more scalable objects, and thus more scalable apps), and can help with inheritance hierarchies.

It's forward engineering. If we took the line of "well it isn't implemented everywhere so let's not get our feet wet", things would move very slowly. On the contrary, early and ambitious adoption helps the industry move forward, helps business decision makers support new technologies, helps developers improve and perfect new ideas and the supporting frameworks. I'm an advocate for early (but precautionary and still backward-compatible) adoption, because experience shows that waiting for enough people to support a technology can leave you waiting far too long. May IE6 be a lesson to those who think otherwise.

Using Object.assign and Object.create for inheritance

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();

What are the benefits / drawbacks of functional object creation in JavaScript?

Alright so I am gonna try and answer my own question here with the information that I have received and additional stuff I have gathered on the internet after asking the question.

TL;DR:

They are both useful and can achieve mostly the same things. Constructors have access to their prototype which can be very useful because it means they have "global" values across all its instances created with the Constructor. It is both useful and potentially dangerous. Useful because all the instances of the Constructor will have access to the same prototype property thus avoiding duplication. Dangerous because you can override the constructor property OR give the instance a property of the same name - making it harder to access the prototypes value.

There is some danger of forgetting new keyword when calling the Constructor but it is easily remedied by adding "use strict"; inside the Constructor function which will then throw an error if you forget the new keyword.

If you want to avoid the Prototype and its features/dangers you can use a Factory
Function.
The really useful Feature of the Functional approach is that you can return anything you like. Rather than always Constructing a "child" of a predefined object.

What I have learned from all this is that it is stupid to pick one over the other when you could be using both. They both have their strengths and weaknesses and people need to remember that Douglas Crockford is just a human being, not the JavaScript God. (That would be Brandon Eich, lol jk!)


The accepted answer by @Domenic on What difference is there in JavaScript between a constructor function, and function returning object which is invoked as a constructor?
Gave me some insights on the differences and similarities between the two methods of object creation.

Constructor

Using the new keyword creates a link between the new object and the Constructor Object it is derived from. The Constructor is the Prototype of the new Object and the new Object is an instance of the Prototype Object.

var Constructor = function () {
this.x = 0;
this.y = 0;
};
var A = new Constructor();
console.log(A instanceof Constructor ); // true

Being linked to the prototype object means that our new object has access to the prototypes properties without having to store them inside the object itself. This is both more memory efficient than creating the properties on each child object and it comes with the added bonus of the power of Prototyping.

Adding a property or method to the object prototype is simple:

Constructor.prototype.color = 'yellow';

Now every object created with the Constructor object has access to the .color property without storing it inside themselves.

var A = new Constructor();
console.log(A.color); // yellow
console.log(A.hasOwnProperty('color')); // false

Since the objects in JavaScript are dynamic it means that you can "retroactively" add new properties to the prototype and objects created before the change will still "inherit" the new properties.

var A = new Constructor();
Constructor.prototype.food = 'bacon';
console.log(A.food); // bacon;

One reason that Crockford might advocate against the Constructor patters is to avoid overriding the prototype property OR overriding the namespace of the prototype inside the child object accidentally.

Constructor.prototype.number = 5;
A.calculate = function () {
return A.number * 5;
}
console.log(A.calculate()); // 25

Constructor.prototype.number = 'fishsticks';
console.log(A.calculate()); // NaN

From what I can understand adding properties after creation will also make the code run slower inside the V8 engine because the objects no longer share the same "hidden classes" But I am not knowledgeable enough to get into that. Breaking the JavaScript Speed Limit with V8

The prototype can still be accessed. Either via the now deprecated .__proto__. or the newObject.getPrototypeOf() method.

console.log(Object.getPrototypeOf(A.color)); // yellow

The other reason why Crockford is advocating against the use of a Constructor Function is that you might forget to type new. If you forget to write new in front of the Constructor it will run the Constructor Function instead of creating a new object.

var A = Constructor();
console.log(A); // undefined

This is easily fixed by adding strict typing to your function which will throw an error if you forget the new keyword.

var Constructor = function () {
"use strict";
this.x = 0;
this.y = 0;
}
var A = Constructor();

console.log(A);
// Uncaught TypeError: Cannot set property 'x' of undefined

Factory Function

I found this pretty straight forward. If you don't want to have deal with the new keyword, and some of the "dangers" of the Constructor function, you can create objects that don't use their prototype with this approach.

function factory () {
var obj = {
x: 0,
y: 0
}
return obj;
}
var A = factory(); // {x: 0, y: 0}

This can be very handy for when you want to do something with the data other than just creating an Object.

function factory () {
if ( new Date().getHours() < 8 ) {
return "can't create object. Need Coffe!"
};
var obj = {
x: 0,
y: 0
}
return obj;
}
var A = factory(); // Before 8 am: "can't create object. Need Coffe!"
var A = factory(); // After 8 am: {x: 0, y: 0};

Doing this you lose the power / danger of the prototype. Because the object is not bound to one.

factory.prototype.foo = "bar";
A = factory();
console.log(A.foo); // undefined

This means you can't use it. But it also means you can't mess it up.

In conclusion.

See TL;DR

I learned a lot searching and writing this, hopefully someone else will learn a thing or two too.

References:

What difference is there in JavaScript between a constructor function, and function returning object which is invoked as a constructor?

Constructor function vs Factory functions

It’s time to start using JavaScript strict mode

What is the difference let o1.prototype = Object.create(o2.prototype) and o1.prototype = o2.prototype?

If you directly assign Parent.prototype to a child's prototype, they'll both be pointing to the same object. So, if you add a method, which only applies to the child class, Parent objects will also have access to them because Parent.prototype === Child.prototype

Example:

function Animal() {};

Animal.prototype.Eat = function() {

console.log("Eating")

}

function Human() {};

Human.prototype = Animal.prototype; // both point to the same object

Human.prototype.Drive = function() {

console.log("Driving")

}

var animal = new Animal();

var human = new Human();

animal.Eat();

human.Eat();

animal.Drive(); // Animals shouldn't be driving

human.Drive();

console.log("animal instanceof Human: ", animal instanceof Human) // true


Related Topics



Leave a reply



Submit