Prototypes in JavaScript are confusing for Everyone
A constructor on any type (Object, String, Array, etc) is initially connected with the Function Object
that creates them. Once the object types value/objects are created, only then are they assigned their own prototypes, which is a unique property and object the Functions constructor creates when each value is created. But the prototyopes of all objects/types (Object, String, Array, etc) in JavaScript starting out are all Function.prototype
. They all derive from functions and their constructors needed to create instances of objects and primitive values in memory! It is not till their values get created by their function constructors that they get assigned their own unique prototypes, both the "prototyoe" property and the Object prototype they inherit from.
This is what 99% of online web pages on the Internet do not tell you!
For example, the Number
(or String Array, Boolean, etc.) type always has a constructor, or Number.constructor
, which derives from the Function Object
assigned to the "Number" type. This is why "Number" is called an "Constructor". So, its prototype is Function.prototype
when you check. Once its function or constructor has built a true Number
primitive or type, its assigned its own unique prototype of Number.prototype
. Let's prove that below!
Here is a simpler explanation. Below is how most objects in JavaScript inherit starting with null down to the object type:
String < Function < Object < null
Array < Function < Object < null
Object < Function < Object < null
Function < Function < Object < null
Here is proof!
Below I am just asking for the prototype found for each object. Note: Object.prototype.toString.call()
just tells us the string name of the prototype:
Object.prototype.toString.call(String);// [object Function]
Object.prototype.toString.call(Array);// [object Function]
Object.prototype.toString.call(Object);// [object Function]
Object.prototype.toString.call(Function);// [object Function]
Object.prototype.toString.call(String.__proto__);// [object Function]
Object.prototype.toString.call(Array.__proto__);// [object Function]
Object.prototype.toString.call(Object.__proto__);// [object Function]
Object.prototype.toString.call(Function.__proto__);// [object Function]
Object.prototype.toString.call(String.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(Array.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(Object.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(Function.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(String.__proto__.__proto__.__proto__);// [object Null]
Object.prototype.toString.call(Array.__proto__.__proto__.__proto__);// [object Null]
Object.prototype.toString.call(Object.__proto__.__proto__.__proto__);// [object Null]
Object.prototype.toString.call(Function.__proto__.__proto__.__proto__);// [object Null]
Please note that the string "[object Function]" is saying that the "prototype" or parent object for the type was "Function.prototype". So it a representation of the underlying prototype parent objects assigned at each level. Now let's explain this in more depth...
A prototype in JavaScript is a word that means the following:
- All Objects in JavaScript ultimately inherit from a series of prototypes or "base classes" that assign their various properties and features through inheritance. This cascades down a tree to the child at the bottom. In JavaScript ALL OBJECTS ultimately inherit from the Object.prototype which is close to the top of that inheritance tree.
- The term "prototype" means a special object with properties and methods inherited by child objects
- "prototype" is also a special property given to all Objects in JavaScript that assigns a given object as the parent prototype to a child but also grants access to changing the prototype. It controls the actual prototype assigned to a child object, but also acts like a true Class Property in that you can use it to manipulate the prototype of a child object. I do not recommend you do this, but you can modify the original Object.prototype inherited by all object by adding new properties using a simple property addition or adding properties via a Object Literal containing properties:
Object.prototype.myproperty = "Hello World";
Object.prototype.myobjectproperties = {text1: "Hello", text2: "World"};
"prototype" the property is expressed in combination with the Child Object name as "MyObjectType.prototype". This new name is now both a identifier for the parent prototype and also a tool to change it. But it is NOT a reference to the actual prototype object! (That is done below using __proto__). It is assigned to all new objects when created of that type. It is first assigned to the function constructor that built the object, then passed to the object the function constructor creates.
"__proto__" is a reference to the actual prototype object assigned to the child. Its also a property but its a reference. So its used to go up the tree of prototypes objects inherited by a child object and access them and their properties. This example below goes up the tree from an object literal created and ends at the top with "null":
alert({}.__proto__.__proto__);// null
Weirdness in Prototype
So, in JavaScript inheritance, everything starts with a Function type! Why is that? Its because you cannot create any of the object "types" (Object, Array, Function, String, etc.) without a Function. And when you do, they still get constructed from a "constructor" called somewhere in a function. A Function and its constructor is what not only creates new objects from types, but also assigns the "prototype" property, the "__proto__" property, and the actual tree of inherited prototypes or objects the child object will use.
There are two states of objects in JavaScript, the "types" and the actual instantiated Objects. That is why "Object" is not the same as a created Object as "const x = {}". And that is why the "type" starts out with a different inheritance or prototypes from its final one.
Check this out!
// The Array type has a prototype of "Function.prototype"
alert(Object.prototype.toString.call(Array));// [object Function]
// But an instance of an Array object has a NEW prototype of "Array.prototype" that the function prototype's constructor created for the object!
const myarray = [];
alert(Object.prototype.toString.call(myarray));// [object Array]
So what happened?
It turns out the FUNCTION CONSTRUCTOR creates and assigns the final prototype
when the object is created. But that custom prototype can be modified both before and after the array object is created with many more properties, objects, etc. So the final assigned prototype is set by the constructor of the Function object which as shown above was the Array types initial prototype.
So realize, the Function.prototype
is the primary prototype of all Object types in JavaScript! It lies underneath all objects, but is a tool to creating the final instantiated object that is assigned its own prototype when created. Note that the "Array.prototype" has a parent prototype of Object.prototype
who has a parent of "null". So the Object.prototype
remains the top parent inherited by all these objects. But in the creation of them the constructor changes the immediate parent of all child objects when new objects get created.
Note that the Function.prototype
gets many of its features from its own Object.prototype it too inherits. The prototype it builds for your created objects are also made from this parent prototype. So in the end. the Object.prototype
provides the goodies needed for Function types and all types to get created and manage the prototype assigned to them. Just remember Function like Object is a special pre-built type with special tools and features needed to create all types of objects!
Last test....lets see how prototype works for CUSTOM OBJECTS we create. The example below proves that the function constructor (part of the Function.prototype) assigns the "prototype" property to the created objects BUT can be customized with various properties and methods before or after being assigned to the objects prototype of MyCustomObject.prototype
. This shows that the final prototype of your object need not be a static copy of the Object.prototype
's inherited properties, but can be something you create that is entirely new!
let newPet;
function Pet() {
this.fourlegs = true;
}
var Cat = {
type : "cat"
}
var Dog = {
type : "dog"
}
// We can see the prototype our constructor created for us
// and modify it as we like! Here we assigned it to an object
// which only means the prototype will merge "Cat" object's
// properties into the Pet.prototype.
Pet.prototype = Cat;
newPet = new Pet();
alert(newPet.type);// cat - inherited the Cat Object's properties in the prototype
Pet.prototype = Dog;
newPet = new Pet();
alert(newPet.type);// dog - inherited the Dog Object's properties in the prototype
alert(newPet.fourlegs);// true - this shows, even though you replace prototype, it ADDs the new types but does NOT erase the existing object properties! This must mean "prototype" is dynamically additive and rebuilt until the final "Pet" prototype is complete.
// Now change the "Pet.prototype" so all new objects have a new property.
Pet.prototype.furcolor = "white";
newPet = new Pet();
alert(newPet.furcolor);// "white"
// So you can see the "Pet.prototype" is dynamic, something you can tell the function constructor to modify!