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.
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.
Classical inheritance is limited to classes inheriting from other classes. However prototypal inheritance includes not only prototypes inheriting from other prototypes but also objects inheriting from prototypes.
Prototypical inheritance allows us to reuse the properties or methods from one JavaScript object to another through a reference pointer function.
Unfortunately, classical inheritance isn't very flexible. This is because inheritance tightly couples classes and their objects. A piece of code is tightly coupled to another piece of code when they are reliant on each other for functionality.
Prototypal inheritance is a form of object-oriented code reuse. Javascript is one of the only [mainstream] object-oriented languages to use prototypal inheritance. Almost all other object-oriented languages are classical.
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:
new
?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.
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:
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:
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:
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.
Object.create
(+1): Construction is separate from initialization.new
operator (-1): Construction and initialization are inseparable.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.
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.
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:
o.constructor
might be different from o
.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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With