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:
The constructor function. This function contains all the logic to create an instance of the "class", i.e. instance specific code.
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 fromShape
.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 fromShape.prototype
.Therefore, this object will not have the
x
andy
own properties.new Shape()
does this:- Creates an object which inherits from
Shape.prototype
. - Calls
Shape
, passing the previous object asthis
. - Returns that object (assuming
Shape
didn't return another object).
Therefore, this object will have the
x
andy
own properties.- Creates an object which inherits from
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
How to Access the Matched Groups in a JavaScript Regular Expression
Uncaught Referenceerror: $ Is Not Defined
Can a Website Detect When You Are Using Selenium With Chromedriver
What Does "This" Refer to in Arrow Functions in Es6
How to Send a Cross-Domain Post Request Via JavaScript
How to Deep Merge Instead of Shallow Merge
Chrome/Firefox Console.Log Always Appends a Line Saying 'Undefined'
Generate Random Number Between Two Numbers in JavaScript
How to Send an Email from JavaScript
JavaScript Variable Number of Arguments to Function
When Should I Use a Return Statement in Es6 Arrow Functions
How to Round to At Most 2 Decimal Places, If Necessary
How to Pass a Parameter to a Settimeout() Callback
Chrome Extension Message Passing: Response Not Sent