Reading some articles from Aadit M Shah like Why Prototypal Inheritance Matters or
Stop Using Constructor Functions in JavaScript from Eric Elliott i think i understand all of their arguments, in theoric. But in practice i don't see the real advantages of this pattern.
Let's take a look two implementations from two snippets to make inheritance.
- First one is using augment.js it's a script from Aadit M Shah
- On this example we are going to use this script. Is made it by Aadit M Shah as well.
Implementation 1:
var AugmentPerson = Object.augment(function() {
this.constructor = function(name) {
this.name = name;
};
this.setAddress = function(country, city, street) {
this.country = country;
this.city = city;
this.street = street;
};
});
var AugmentFrenchGuy = AugmentPerson.augment(function(base) {
this.constructor = function(name) {
base.constructor.call(this,name);
};
this.setAddress = function(city, street) {
base.setAddress.call(this, "France", city, street);
};
});
var AugmentParisLover = AugmentFrenchGuy.augment(function(base) {
this.constructor = function(name) {
base.constructor.call(this, name);
};
this.setAddress = function(street) {
base.setAddress.call(this, "Paris", street);
};
});
var t = new AugmentParisLover("Mary");
t.setAddress("CH");
console.log(t.name, t.country, t.city, t.street); //Mary France Paris CH
In this example we are using function constructors instead of inherit directly from a object.
Implementation 2:
var CreatePerson = {
create: function (name) {
this.name = name;
return this.extend();
},
setAddress: function(country, city, street) {
this.country = country;
this.city = city;
this.street = street;
}
};
var CreateFrenchGuy = CreatePerson.extend({
create: function (name) {
return CreatePerson.create.call(this,name);
},
setAddress: function(city, street) {
CreatePerson.setAddress('France', city, street);
}
});
var CreateParisLover = CreateFrenchGuy.extend({
create: function (name) {
return CreateFrenchGuy.create.call(this,name);
},
setAddress: function(street) {
CreateFrenchGuy.setAddress('Paris', street);
}
});
var t = CreateParisLover.create("Mary");
t.setAddress("CH");
console.log(t.name, t.country, t.city, t.street); //Mary France Paris CH
To be honest, i'm trying to see the benefits of the second implementation. But i am not able. The only point i see is more flexible is because we can create the instance using apply:
var t = CreateParisLover.create.apply(CreateParisLover, ["Mary"]);
This give us more flexibility, it's true. But we can do the same with this:
Function.prototype.new = function () {
function functor() { return constructor.apply(this, args); }
var args = Array.prototype.slice.call(arguments);
functor.prototype = this.prototype;
var constructor = this;
return new functor;
};
Then we can:
var t = AugmentParisLover.new.apply(AugmentParisLover, ["Mary"]);
What is the real benefits in terms of flexibility, re-usability, difficulty...
Because if you check the performance of both cases. Object.create() is pretty much slower than new: http://jsperf.com/inheritance-using-create-vs-new
I'm confusing.
Similar questions have been asked and answered many times before. See:
Constructor function vs Factory functions
Classical Vs prototypal inheritance
More learning:
https://medium.com/javascript-scene/3-different-kinds-of-prototypal-inheritance-es6-edition-32d777fa16c9#.s0r3i5w6t
http://vimeo.com/69255635
tl;dr
- Constructors break the open / closed principle
- Constructors conflate object creation with object initialization - sometimes hampering the reusability of the code
- Constructors look a bit like classes, which is confusing. JavaScript doesn't need classes (I recommend avoiding the class keyword coming in ES6). JavaScript has something better than classes.
- The combination of prototype delegation and dynamic object extension (concatenative inheritance) is much more powerful and flexible than classical inheritance.
- The connections between the Constructor.prototype and instances are frail and untrustworthy in JavaScript. Using constructors can provide the illusion of a working instanceof, which could be confusing when it doesn't work across execution contexts, or doesn't work if the constructor prototype gets swapped out.
- Swapping out the prototype is harder with constructors. You may want to do that to enable polymorphic object construction. With factories, hot swappable prototypes are easy, and can be done using .call() and .apply().
Edit - responding to the "answer" posted by the OP:
The best thing about Object.create is that it's a dedicated, low-level tool that lets you create a new object and assign any prototype you want to it without using a constructor function. There are lots of reasons to avoid constructors, covered in-depth here: Constructor function vs Factory functions
- The code you use to demonstrate "less code" doesn't really demonstrate the difference between classical and prototypal inheritance at all. A more typical example might look like:
Classical
var Animal = function Animal(name) {
this.name = name;
};
Animal.prototype.walk = function walk() {
console.log(this.name + ' goes for a walk.');
};
var Rabbit = function Rabbit(/* name */) {
// Because construction and instantiation are conflated, you must call super().
Animal.prototype.constructor.apply(this, arguments);
};
// Classical inheritance is really built on top of prototypal inheritance:
Rabbit.prototype = Object.create(Animal.prototype);
// Fix the .constructor property:
Rabbit.prototype.constructor = Rabbit;
Rabbit.prototype.jump = function jump() {
console.log(this.name + ' hops around a bit.');
};
var myRabbit = new Rabbit('Bunny George');
myRabbit.walk();
// Bunny George goes for a walk.
Prototypal
var animalMethods = {
walk: function walk() {
console.log(this.name + ' goes for a walk.');
}
};
var animal = function animal(name) {
var instance = Object.create(animalMethods);
instance.name = name;
return instance;
};
var rabbitMethods = {
jump: function jump() {
console.log(this.name + ' hops around a bit.');
}
};
var rabbit = function rabbit(name) {
var proto = rabbitMethods;
// This is more commonly done like mixin({}, animalMethods, rabbitMethods);
// where mixin = $.extend, _.extend, mout.object.mixIn, etc... It just copies
// source properties to the destination object (first arg), where properties from
// the last argument override properties from previous source arguments.
proto.walk = animalMethods.walk;
var instance = Object.create(rabbitMethods);
// This could just as easily be a functional mixin,
// shared with both animal and rabbit.
instance.name = name;
return instance;
};
var rabbit2 = rabbit('Bunny Bob');
rabbit2.walk();
// Bunny Bob goes for a walk.
The amount of code required is pretty similar, but to me, it's a LOT more clear what the prototypal stuff is doing, and it's also a lot more flexible, and has none of the classical inheritance arthritic baggage of the first example.
Programming is a lot like fashion. Subconsciously most programmers write code which to them looks aesthetically pleasing. This is the main reason why Java programmers want to implement classical inheritance in JavaScript. Yes, trying to implement classical inheritance in JavaScript is a monolithic task but that doesn't stop people from doing it. It's an overkill but people still do it because they just want their code to look like classes (e.g. jTypes).
In much the same way Eric and I have been trying to popularize the use of factory functions instead of constructor functions. However this shift from factories to constructors is not just for aesthetic reasons. The two of us are trying to change the mentality of JavaScript programmers because in certain aspects we both believe that JavaScript is fundamentally flawed. The new
operator in JavaScript is one such aspect. Although it's broken yet it's central to the language and hence it cannot be avoided.
The bottom line is this:
If you want to create prototype chains in JavaScript then you have to use new
. There is no other way around it (except .__proto__
which is frowned upon).
Interestingly you need neither prototypes nor classes to inherit from multiple objects. Using object composition you can achieve strong behavioral subtyping in JavaScript as Benjamin Gruenbaum describes in the following answer: https://stackoverflow.com/a/17008693/783743
In this answer I'll touch upon the following topics:
- Why are we stuck with
new
?
- Why are factories better than constructors?
- How do we get the best of both worlds?
1. Why are we stuck with new
?
The new
keyword is put on a pedestal in JavaScript. There's no way to create a prototype chain in JavaScript without using new
. Yes you can change the .__proto__
property of an object but only after it's created, and that practice is frowned upon. Even Object.create
uses new
internally:
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F;
};
As Douglas Crockford mentioned:
The Object.create
function untangles JavaScript's constructor pattern, achieving true prototypal inheritance. It takes an old object as a parameter and returns an empty new object that inherits from the old one. If we attempt to obtain a member from the new object, and it lacks that key, then the old object will supply the member. Objects inherit from objects. What could be more object oriented than that?
The point is that although the new
keyword in JavaScript is "tangled" up there's no other way to create prototype chains in JavaScript. The Object.create
function, even when implemented natively, is still slower than using new
and hence for performance reasons alone most people still use new
even though Object.create
is a more logically sound option.
2. Why are factories better than constructors?
Now you might wonder whether new
is really so bad. After all performance wise it is indeed the best solution. In my opinion however it shouldn't be so. Whether you use new
or Object.create
performance should always be the same. This is where the language implementations are lacking. They should really strive towards making Object.create
faster. So besides performance does new
have any other redeeming qualities? In my humble opinion it doesn't.
Oftentimes you don't really know what's wrong with a language until you start using a better language. So let's see some other languages:
a) Magpie
Magpie is a hobby language created by Bob Nystrom. It has a bunch of very interesting features which interact very nicely with each other, namely:
- Patterns
- Classes
- Multimethods
Classes in Magpie however are more akin to prototypes in JavaScript or data types in Haskell.
In Magpie instantiation of classes is split into two steps:
- Constructing a new instance.
- Initializing the newly constructed instance.
In JavaScript the new
keyword combines the construction and the initialization of instances. This is actually a bad thing because as we'll see soon splitting construction and initialization is actually a good thing.
Consider the following Magpie code:
defclass Point
var x
var y
end
val zeroPoint = Point new(x: 0, y: 0)
def (this == Point) new (x is Int, y is Int)
match x, y
case 0, 0 then zeroPoint
else this new(x: x, y: y)
end
end
var origin = Point new(0, 0)
val point = Point new(2, 3)
This is equivalent to the following JavaScript code:
function Point(x, y) {
this.x = x;
this.y = y;
}
var zeroPoint = new Point(0, 0);
Point.new = function (x, y) {
return x === 0 && y === 0 ?
zeroPoint : new Point(x, y);
};
var origin = Point.new(0, 0);
var point = Point.new(2, 3);
As you can see here we've split the construction and the initialization of instances into two functions. The Point
function initializes the instance and the Point.new
function constructs the instance. In essence we have simply created a factory function.
Separating construction from initialization is such a useful pattern that the good people of the JavaScript room have even blogged about it, calling it the Initializer Pattern. You should read about the initializer pattern. It shows you that initialization in JavaScript is separate from construction.
- Factories like
Object.create
(+1): Construction is separate from initialization.
- The
new
operator (-1): Construction and initialization are inseparable.
b) Haskell
JavaScript has been my favorite language since the past 8 years. Recently however I started programming in Haskell and I must admit that Haskell has stolen my heart. Programming in Haskell is fun and exciting. JavaScript still has a long way to go before it'll be in the same league as Haskell and there's much that JavaScript programmers can learn from Haskell. I would like to talk about algebraic data types from Haskell apropos to this question.
Data types in Haskell are like prototypes in JavaScript and data constructors in Haskell are like factory functions in JavaScript. For example the above Point
class would be written as follows in Haskell:
data Point = Point Int Int
zeroPoint = Point 0 0
origin = zeroPoint
point = Point 2 3
Succinct isn't it? However I'm not here to sell Haskell so let's take a look at some other features Haskell offers:
data Shape = Rectangle Point Point | Circle Point Int
rectangle = Rectangle origin (Point 3 4)
circle = Circle zeroPoint 3
Here rectangle
and circle
are both instances of type Shape
:
rectangle :: Shape
circle :: Shape
In this case Shape
is our prototype (data type in Haskell) and rectangle
and circle
are instances of that data type. More interestingly however the Shape
prototype has two constructors (data constructors in Haskell): Rectangle
and Circle
.
Rectangle :: Point -> Point -> Shape
Circle :: Point -> Int -> Shape
The Rectangle
data constructor is a function which takes a Point
and another Point
and returns a Shape
. Similarly the Circle
data constructor is a function which takes a Point
and an Int
and returns a Shape
. In JavaScript this would be written as follows:
var Shape = {};
Rectangle.prototype = Shape;
function Rectangle(p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
Circle.prototype = Shape;
function Circle(p, r) {
this.p = p;
this.r = r;
}
var rectangle = new Rectangle(origin, Point.new(3, 4));
var circle = new Circle(zeroPoint, 3);
As you can see a prototype in JavaScript can have more than one constructor and that makes sense. It's also possible for one constructor to have different prototypes at different instances of time but that makes no sense at all. Doing so would break instanceof
.
As it turns out having multiple constructors is a pain when using the constructor pattern. However it's a match made in heaven when using the prototypal pattern:
var Shape = {
Rectangle: function (p1, p2) {
var rectangle = Object.create(this);
rectangle.p1 = p1;
rectangle.p2 = p2;
return rectangle;
},
Circle: function (p, r) {
var circle = Object.create(this);
circle.p = p;
circle.r = r;
return circle;
}
};
var rectangle = Shape.Rectangle(zeroPoint, Point.new(3, 4));
var circle = Shape.Circle(origin, 3);
You could also use the extend
function from my blog post on Why Prototypal Inheritance Matters to make the above code more succinct:
var Shape = {
Rectangle: function (p1, p2) {
return this.extend({
p1: p1,
p2: p2
});
},
Circle: function (p, r) {
return this.extend({
p: p,
r: r
});
}
};
var rectangle = Shape.Rectangle(zeroPoint, Point.new(3, 4));
var circle = Shape.Circle(origin, 3);
Factories written in this way look a lot like the module pattern and it feels natural to write code like this. Unlike with the constructor pattern everything is wrapped up nicely in an object literal. Nothing is dangling here, there and everywhere.
Nevertheless if performance is your main concern then stick with the constructor pattern and new
. In my opinion however modern JavaScript engines are fast enough that performance is no longer the main factor. Instead I think JavaScript programmers should invest more time in writing code that's maintainable and robust and the prototypal pattern is indeed more elegant and understandable than the constructor pattern.
- Factories (+1): You can easily create multiple factories for each prototype.
- Constructors (-1): Creating multiple constructors for each prototype is hacky and clumsy.
- Prototypal Pattern (+1): Everything is encapsulated within a single object literal. Looks a lot like the module pattern.
- Constructor Pattern (-1): It's unstructured and looks incohesive. Difficult to understand and maintain.
In addition Haskell also teaches us about pure functional programming. Since factories are simply functions we can call
and apply
factories, compose factories, curry factories, memoize factories, make factories lazy by lifting them and much more. Because new
is an operator and not a function you can't do that using new
. Yes you can make a functional equivalent of new
but then why not just use factories instead? Using the new
operator in some places and the new
method in other places is inconsistent.
3. How do we get the best of both worlds?
Alright so factories do have their advantages, but still the performance of Object.create
sucks doesn't it? It does, and one of the reasons is because every time we use Object.create
we create a new constructor function, set its prototype to the prototype we want, instantiate the newly created constructor function using new
and then return it:
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F;
};
Can we do better than this? Let's try. Instead of creating a new constructor every time why don't we just instantiate the .constructor
function of the given prototype?
Object.create = function (o) {
return new o.constructor;
};
This works in most cases but there are a few problems:
- The prototype of
o.constructor
might be different from o
.
- We only want to construct a new instance of
o
, but o.constructor
might have initialization logic as well which we can't separate from the construction.
The solution is pretty simple:
function defclass(prototype) {
var constructor = function () {};
constructor.prototype = prototype;
return constructor;
}
Using defclass
you can create classes as follows:
var Shape = defclass({
rectangle: function (p1, p2) {
this.p1 = p1;
this.p2 = p2;
return this;
},
circle: function (p, r) {
this.p = p;
this.r = r;
return this;
}
});
var rectangle = (new Shape).rectangle(zeroPoint, Point.new(3, 4));
var circle = (new Shape).circle(origin, 3);
As you can see we've separated construction and initialization and the initialization can be deferred to multiple constructors. It can even be chained as follows: (new Shape).rectangle().circle()
. We've replaced Object.create
with new
which is much faster and we still have the flexibility to do whatever we want. In addition everything is nicely encapsulated within a single object literal.
Conclusion
As you can see the new
operator is a necessary evil. If new
was a implemented as a factory function then that would be great but it's implemented as an operator instead and operators in JavaScript are not first class. This makes it more difficult to do functional programming with new
. Factories on the other hand are flexible. You can tailor make any number of factory functions for your prototypes and the ability to do whatever you want is the biggest selling point of factory functions.
Thank you for your amazing answer. But, I am not agree with most of your afirmation. Without see the equivalent in both patterns. Some arguments are subjetive for me. So i would like to focus on facts. And in this answer I am going to comment what are the best points of each. And without external libraries/snippets because then we can fight about which library is better.
Good points about Object.create
1. Create the instance/object using call/apply
var Shape = {
Rectangle: function (p1, p2) {
var rectangle = Object.create(this);
rectangle.p1 = p1;
rectangle.p2 = p2;
return rectangle;
},
Circle: function (p, r) {
var circle = Object.create(this);
circle.p = p;
circle.r = r;
return circle;
}
};
var rectangle = Shape.Rectangle.call(Shape, zeroPoint, Point.new(3, 4));
var circle = Shape.Circle.call(Shape, origin, 3);
This is not possible using the new
.
Good points about new
1. Less code
function Rectangle(p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
function Circle(p, r) {
this.p = p;
this.r = r;
}
vs
Rectangle: function (p1, p2) {
var rectangle = Object.create(this);
rectangle.p1 = p1;
rectangle.p2 = p2;
return rectangle;
},
Circle: function (p, r) {
var circle = Object.create(this);
circle.p = p;
circle.r = r;
return circle;
}
Whatever case you always have to write more code to implement the same.
2. Easier to maintain
var rectangle = Shape.Rectangle(zeroPoint, Point.new(3, 4));
var circle = Shape.Circle(origin, 3);
What happen if tomorrow you want to change the name of Shape to Geometry? You have to review all your code and change all the words Shape for each instantiation of cicle or rectangle. This point is more remarkable when you are doing inheritance. Because you always have to call exacly the same name of the constructor to access to the super.methods
Bonus. Easier to read or understand (This is subjetive)
If in my code I'm seeing new Rectangle(...)
i know I'm creating a new instance of the object Rectangle. However Shape.Rectangle(...)
don't tell me if is a new object or if is just a function or if Shape is a unique instance like var Shape = new Whatever()
.
3. Private properties and methods
var Person = function() {
var a = 5;
this.method = function (b) { return a*b; };
};
var obj = new Person;
obj.method(5); // return 25
obj.a; // return undefined
vs
var Person = {
a: 5,
method: function (b) { return this.a*b; }
};
var obj = Object.create(Person);
obj.method(5); // return 25
obj.a; // return 5
You always can have private methods and properties on the new
pattern. On the object.create
pattern you can if your implementation is specific. If you do this, your code is more difficult and verbose to write (But this is a personal opinion).
4. I can pass paramenter on the constructor
var Person = function(a) {
this.method = function (b) { return a*b; };
};
var obj = new Person(5);
obj.method(5); // return 25
vs
var Person = {
method: function (b) { return a*b; }
};
var obj = Object.create(Person);
obj.method(4); //Error
5. instanceof
No way to do instanceof natively with the Object.create
pattern.
6. Performance
I leave this for the last one, because most of the other points can be solved with extra javascript. But in this case can't be the same to the new
pattern. Also i think this is the most important advantage for new
pattern. Because if you are programming for browser the performance sometimes doesn't matter. But if you are working of the backend and you are making a very big and scalable app, the performance matter. Why Paypal leave java and go to node.js? Because the performance it's very important in big projects.
Conclusion
So, if new
it's 10 times faster than Object.create
. I think only for this reason, and only for this reason worth keep programming with new.
Besides, give me more flexibility and i can do things with new
pattern that I can't with Object.create
.
And I agree that the nature of the Javascript is to using the Object.create
. But i think i get more benefits if i use new
.
Sorry for my English.