ref – http://aaditmshah.github.io/why-prototypal-inheritance-matters
execute as constructor function vs standard function
In javascript, we have the “new” keyword, which is used with functions in order to use it as a constructor function.
If you use it without the “new” keyword, you are simply calling it as a standard function.
Problem with using “new”
We create a function Person. Then use new to call it as a “constructor” function.
1 2 3 4 5 6 |
function Person(newFirst, newLast) { this.first = newFirst; this.last = newLast; } var author = new Person("Perlman","Atlas") |
But there’s a problem. We can’t use any of Function’s prototype functions such as “apply”, “call”, etc.
If we do make “new” into a function, then we can use “apply”, “call”…etc:
1 |
var author = Person.new.apply(Person, ["Perlman", "Atlas"]); |
JS has prototypal inheritance, so its possible to implement “new” as a function:
1 2 3 4 5 6 7 |
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; }; |
“new” cannot be used in conjunction with functional feature.
“new” masks true prototypal inheritance in JS.
JS is a prototypal language, try not to use “new”
Create objects “out of nothing”
1 |
var obj = Object.create(null); // object has no __proto__. clone of null |
Cloning Existing Object
a)
There is an existing object.
1 2 3 4 5 |
var rectangle = { area : function() { return this.month * this.height; } } |
We create an empty object where its prototype is that of the existing object.
1 |
var rect = Object.create(rectangle); |
b)
We create an empty object that has __proto__ pointing to Object’s prototype.
Then we attach a property called “area” as a function.
1 2 3 4 |
var rectangle = Object.create(Object.prototype); rectangle.area = function () { return this.width * this.height; }; |
Extending a Newly Created Object
In the above example, we cloned the rectangle object and called it rect, but before we may use the area function of rect we need to extend it with width and height.
We add properties width and height and initialize numbers to the properties.
Then, we are able to call the “area” function.
1 2 3 4 |
rect.width = 5; rect.height = 10; alert(rect.area()); |
However, this way is not good because we need to manually define width and height on every clone of rectangle
It would be nice to have a function create a clone of rectangle and extend it with width and height properties for us.
Prototypal Pattern
Notice the create function. It uses Object.create(this). This allocates a new empty object, that has __proto__ pointing to rectangle.
This newly created object is referenced by self.
Then, self attaches properties height and width to it, and initialize them to the parameters width/height.
It returns the newly allocated object. Thus, when you use the create function, you literally create a new object with its __proto__ pointing to rectangle. That way, your allocated object can call function area via prototype.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var rectangle = { create: function (width, height) { var self = Object.create(this); // allocates object that has __proto__ pointing to rectangle // attach properties to newly created object self.height = height; self.width = width; return self; }, area: function () { return this.width * this.height; } }; var rect = rectangle.create(5, 10); |
Then can call prototype functions.
1 |
alert(rect.area()); |
Prototypal Pattern vs Constructor Pattern
What we had previous is a Prototypal pattern.
We we have here, is a constructor pattern:
1 2 3 4 5 6 7 8 9 10 11 12 |
function Rectangle(width, height) { this.height = height; this.width = width; } Rectangle.prototype.area = function () { return this.width * this.height; }; var rect = new Rectangle(5, 10); alert(rect.area()); |
In order to make JavaScript look more like Java, the prototypal pattern was inverted to yield the constructor pattern.
Hence every function in JavaScript has a prototype object and can be used as a constructor.
In addition, it clones the prototype of the constructor and binds it to the “this” pointer of the constructor, returning this if no other object is returned.
Both the prototypal pattern and the constructor pattern are equivalent
Hence you may wonder why anybody would bother using the prototypal pattern over the constructor pattern. After all the constructor pattern is more succinct than the prototypal pattern. Nevertheless the prototypal pattern has many advantages over the constructor pattern, enlisted in the following table:
Constructor Pattern
- Functional features (call, apply…etc) can’t be used in conjunction with the new keyword
- Forgetting to use new leads to unexpected bugs and global variables
- Prototypal inheritance is unnecessarily complicated and confusing. (http://chineseruleof8.com/code/index.php/2018/02/06/prototype-and-inheritance-in-js-non-class/)
- Functional features (call, apply…etc) can be used in conjunction with create
- Since create is a function the program will always work as expected
- Prototypal inheritance is simple and easy to understand
Prototypal Pattern
The last point may need some explanation.
The underlying idea is that prototypal inheritance using constructors is more complicated than prototypal inheritance using prototypes.
prototypal inheritance using prototypes
1 2 3 4 5 6 7 8 9 |
var square = Object.create(rectangle); square.create = function (side) { return rectangle.create.call(this, side, side); }; var sq = square.create(5); alert(sq.area()); |
First we create a clone of rectangle and call it square.
Next we override the create function of square with a new create function.
Finally we call the create function of rectangle from the new create function and return the object is returns.
prototypal inheritance using constructors
Using Square as a constructor function, we must call Rectangle.call in order to initialize “this” like a super object.
That way, when we use new to create it, we’ll work off of a Rectangle object as a super object. Then we can implement whatever we want
additionally at Square as the child object.
You can read in detail at http://chineseruleof8.com/code/index.php/2018/02/06/prototype-and-inheritance-in-js-non-class/ (scroll to the very bottom and see “custom example”)
1 2 3 4 5 6 7 8 9 10 11 |
function Square() { Rectangle.call(this, side, side); } Square.prototype = Object.create(Rectangle.prototype); Square.prototype.constructor = Square; var sq = new Square(5); alert(sq.area()); |
Sure, the constructor function becomes simpler. However it becomes very difficult to explain prototypal inheritance to a person who knows nothing about it.
It becomes even more difficult to explain it to a person who knows classical inheritance.
When using the prototypal pattern it becomes obvious that one object inherits from another object. When using the constructor pattern this is not so obvious because you tend to think in terms of constructors inheriting from other constructors.
Combining Object Creation and Extension
First, we want something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var rectangle = { create: function (width, height) { return this.extend({ height: height, width: width }); }, area: function() { return this.width * this.height; } } // used like so var rect = rectangle.create(5, 10) // where rect's __proto__ is pointing to rectangle // and its object contents is the literal object we passed into extend (in create function) |
We want to create a new object (with its default functionalities as “this”), but extend it with the properties as mentioned.
so we pass the extension like so:
1 2 3 4 |
{ height: height, width: width } |
into the extend function. Notice extend function is attached to Object.prototype.
This is because we want all objects to have this functionality.
1 2 3 4 5 6 7 8 |
Object.prototype.extend = function (extension) { console.log(extension); // { height: 10, width: 5 } console.log(extension.__proto__) // { extend: [Function] } ... ... ... } |
We then log to look at the extension. It is as expected. We see that the extension we passed in has the properties height and width, and initiated to 10, and 5.
The default functionalities are all in “this”, aka, rectangle.
We then use default Object’s hasOwnProperty, which says:
The hasOwnProperty() method returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it).
Hence, we just have a reference to it to transfer properties from the extension over. And not touch any inherited ones.
1 2 3 4 5 6 7 |
Object.prototype.extend = function (extension) { console.log(extension); // { width: 10, height: 10 } console.log(extension.__proto__) // { extend: [Function] } // this is because we've implemented extend on Object.prototype // just a reference to hasOwnProperty var hasOwnProperty = Object.hasOwnProperty; |
Then, we create a new empty object, with its __proto__ pointing to “this”. Which is rectangle.
As you can see, the object created is empty, with its proto pointing to the Rectangle “this”.
1 2 3 4 5 6 |
var object = Object.create(this); console.log("---- allocated object ----") console.log("object:"); console.log(object) // {} console.log("object.__proto__:"); console.log(object.__proto__) // { area: [Function] } |
output:
—- allocated object —-
object:
{}
object.__proto__:
{ create: [Function: create], area: [Function: area] }
In the next step, we’re gunna assign all the properties from the extension object over to our allocated object.
1) We loop over all the properties in the extension. We’ll get height, width, extend. height and width were the original properties owned by the “extension” object we passed into the extend function. We’ll also get extend, which is the function we attached to Object.prototype
We call the hasOwnProperty, which decides whether the property is owned the extension object. Properties width and height are. However, extend is not owned. It it extend via Object.prototype.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
for (var property in extension) { console.log("--|" + property + "|--"); // is width owned by the extension object? yes // is height owned by the extension object? yes // is extend owned by the extension object? no. It is inherited. // point is: simply transfer over its owning properties // don't touch inherited properties console.log(hasOwnProperty.call(extension, property)); } |
output:
–|height|–
true
–|width|–
true
–|extend|–
false
2) Hence, if the property is owned by the extension object, and it does not exist in the allocated object, then we simply attach it.
1 2 3 4 5 6 |
// allocated object will attach property and value same as extension if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") { // then have object take on that exact same property object[property] = extension[property]; } |
Here, we have the full code. What is returned is a newly allocated object that has the extension properties transferred over. With its __proto__ pointing to “this”, which is rectangle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Object.prototype.extend = function (extension) { var hasOwnProperty = Object.hasOwnProperty; var object = Object.create(this); for (var property in extension) { if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") { // then have object take on that exact same property object[property] = extension[property]; } } return object; }; |
Hence in order to use it, we’ll have rectangle’s create function implement it like so:
1 2 3 4 5 6 7 8 9 10 11 12 |
var rectangle = { create: function (width, height) { // return an object were 'this' is referencing rectangle return this.extend({ height: height, width: width }); }, area: function() { return this.width * this.height; } } |
Then use it like so:
1 2 3 4 |
var rect = rectangle.create(5, 10) console.log(rect) console.log(rect.__proto__) console.log(rect.area()) |
Hence, as you can see, the properties from the extension object is copied over to the allocated object. The allocated object’s __proto__ points to rectangle.
output:
{ height: 10, width: 5 }
{ create: [Function: create], area: [Function: area] }
50
Some of you may have noticed that the object returned by the extend function actually inherits properties from two objects, and not one – the object being extended and the object extending it. In addition the way in which properties are inherited from these two objects is also different.
In one part, case we inherit properties via delegation. In another, we case we inherit properties via concatenation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Object.prototype.extend = function (extension) { var hasOwnProperty = Object.hasOwnProperty; var object = Object.create(this); // inherit properties via delegation for (var property in extension) { if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") { object[property] = extension[property]; // inherit properties via concatenation } } return object; }; |
Differential Inhereitance
It operates on the principle that most objects are derived from other, more general objects, and only differ in a few small aspects; while usually maintaining a list of pointers internally to other objects which the object differs from.
Prototypal inheritance in JavaScript is based on differential inheritance. Every object in JavaScript has an internal pointer, called [[proto]], which points to the prototype of that object. The [[proto]] property of objects created “out of nothing” point to null. This forms a chain of objects linked via the internal [[proto]] property (hence called a prototype chain), which ends in null.
When you try to access a property of an object the JavaScript engine first searches for that property on the object itself. If it cannot find the property on the object then it delegates the property access to the prototype of the object. In this way the property access traverses up the prototype chain until the property is found (in which case it’s returned) or the chain ends in null (in which case undefined is returned).
TODO – put diagram of hero hierarchy here. put reference here
Cloning or Concatenate Inheritance
Most JavaScript programmers would argue that copying the properties of one object to another is not truly a form of inheritance because any modifications to the original object are not reflected on the clone.
However, modifications to the original object can be propagated to its copies to achieve true prototypal inheritance.
Concatenation and delegation both have their advantages and disadvantages. They are listed in the following table:
Delegation
In JavaScript, an object may have a link to a prototype for delegation. If a property is not found on the object, the lookup is delegated to the delegate prototype, which may have a link to its own delegate prototype, and so on up the chain until you arrive atObject.prototype
, which is the root delegate. This is the prototype that gets hooked up when you attach to a Constructor.prototype
and instantiate withnew
. You can also use Object.create()
for this purpose, and even mix this technique with concatenation in order to flatten multiple prototypes to a single delegate, or extend the object instance after creation.
1 |
var a = Object.create(Hero.prototype) |
Any changes to the prototype are automatically reflected on all its clones.
TODO – use picture
Property access is slower because it may need to traverse up the prototype chain.
TODO – use picture
Objects may only delegate to a single prototype in JavaScript.
TODO – use picture
Concatenative Inheritance
Concatenative inheritance is the process of combining the properties of one or more source objects into a new destination object. Believe it or not, it is the most commonly used form of inheritance in JavaScript.
1 2 3 |
if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") { object[property] = extension[property]; // inherit via concatenation } |
Any changes to the prototype need to be propagated to all its clones.
Property access is faster because inherited properties are copied.
Objects may copy properties from any number of prototypes.
Inheriting from Multiple Prototypes
This is an important feature because it proves that prototypal inheritance is more powerful than classical inheritance in Java and as powerful as classical inheritance in C++. To implement multiple inheritance in the prototypal pattern you only need to modify the extend function to copy the properties of multiple prototypes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Object.prototype.extend = function (arguments) { var hasOwnProperty = Object.hasOwnProperty; var object = Object.create(this); var length = arguments.length; var index = length; while (index) { var extension = arguments[length - (index--)]; for (var property in extension) if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") object[property] = extension[property]; // concatenative inheritance } return object; }; |