eolas/neuron/acc60df2-7719-47eb-91e2-341f930a4aea/Prototypical_inheritance.md

267 lines
9.5 KiB
Markdown
Raw Normal View History

2024-12-09 18:34:15 +00:00
---
tags: [javascript, OOP]
---
# Prototypical inheritance
## Object oriented basis of JavaScript and nomenclature
As we know, everything in JS that isn't a primitive (i.e. a primitive data type:
string, number, boolean, null) is an object. Where an object is a data structure
that consists in key:value pairs.
The key is always a string and the value can be: a primitive value, a method
(function within an object), an array, another object etc.
> In other object-oriented languages like C# and Java, **functions are called
> methods** however in JS the term **method** is reserved for function that
> exists as a property of an objects that contains it.
### Lack of classes in JS
In contrast to more advanced object-oriented languages that are compiled (Java,
C#), classes do not really exist in JavaScript. This is due to the
prototype-based nature of JS (as we will see) however class-like functionality
can be achieved through the use of functions.
Remember in JS that functions can be simple code blocks that execute when called
(which is what we mean most of the time by 'function') **or they can be
constructor functions which create generic object types from which specific
token instances can be generated as objects.**
It is through these 'generic object types' that something akin to a class can be
created in JavaScript. (These are sometimes called function-objects )
The following is a good account of the differences between prototypal and
class-based languages:
> JavaScript is a prototype-based language, **meaning object properties and
> methods can be shared through generalized objects that have the ability to be
> cloned and extended**. This is known as **prototypical inheritance** and
> differs from **class inheritance**. Among popular object-oriented programming
> languages, JavaScript is relatively unique, as other prominent languages such
> as PHP, Python, and Java are class-based languages, which instead **define
> classes as blueprints for objects**.
### Example of a constructor function
In order to understand prototypes in a moment, it will be helpful to have an
example of a constructor function that can be used to generate object instances:
```jsx
/* Create constructor function: */
function Band(mainAlbum, subgenre) {
// use Pascal for constructor function names
this.mainAlbum = mainAlbum;
this.subgenre = subgenre;
}
/* Then to create an object instance: */
var slayer = new Band("Reign in Blood", "thrash metal");
```
## Prototypes
Now that we know class-type functionality enters JS via constructor functions
and we have an example, we can look at prototypes.
> Prototypes are a special type of object that exist as a property within
> function-objects.
Whenever we create an instance-object from constructor function using `new`, the
object is given a property with the key `_proto_` . It's value is the
`[[prototype]]` property of the function that created, i.e the class of which
the object is an instance:
```jsx
{
_proto_: prototype;
}
```
Note that we use the syntax `[[prototype]]` to indicate that **whilst the value
exists, it is not accessible via JavaScript code**, in the manner of a normal
object-literal or function-object, because it is the lower-level foundation of
first-order JS code.
So how would this work for the `slayer` object we created above?
```jsx
Slayer._proto_ === Band.prototype;
```
The thing is, you do not tend to see this prototype functionality and it is not
possible to refer to it as a key:value directly. This is because it is something
under-the-hood - it is a process that makes the object-oriented aspects of JS
possible but it is not apparent on the first-order level of everyday coding.
The prototype is like a hidden hook that facilitates the generation of objects
from class-like constructor functions. With our new awareness of it, we are in a
position _to understand how JavaScript works in foundational terms._
## Every non-primitive data type in JS possesses the `Object` prototype
The title above indicates how and why JavaScript establishes its object-oriented
data structure.
When we create an object literal (i.e an every day object not a class-like
function), under-the-hood JS is making a `new Object()` call in much the same
way as we are calling the function-object `Band` when we declare
`var slayer = new Band(...)` .
All instance-objects in JavaScript (which, remember is the set of everything
that is not a primitive) **inherit from the `Object` keyword**. And, to do this,
prototypes are necessary. Now we see how integral prototypes are to JavaScript.
### Examples
Here we create an object-literal and uncover how it has it's basis in the
overall `Object` constructor.
```jsx
var slayerMembers = {
bassist: "Tom Araya",
guitarist: "Kerry King",
guitarist: "Jeff Hanneman",
drummer: "Dave Lombardo",
};
// slayerMembers._proto_ === Object.prototype
```
Here we create an instance-object off of our `Band` constructor function and
trace it back to `Object`
```jsx
var deicide = new Band("Legion", "death metal");
/* deicide object relies on Band prototype:
deicide._proto_=== Band.prototype
Band prototype in turn relies in turn on Object prototype:
Band.prototype._proto_=== Object.prototype
*/
```
As the above examples show, ultimately all objects in JavaScript (whether
variables, arrays, methods, or functions) eventually lead back to
`Object.prototype` .
### How the browser uses prototypes when interpreting code
When the browser interprets your code attempting to access a property or method
of an object it runs through the following process:
1. Search on the object itself. If fails →
2. Search the object's prototype. If fails →
3. Search the prototype of the linked object. If fails→
4. Keep going until `Object.prototype` is reached
Clearly with each step we are going to a lower level of abstraction, drawing the
curtain back more and more until we got the point where JavaScript effectively
stops because any lower would take you into its lower-level parent language
(C/C++ depending on how the specific browser executes ECMAScript)
Note (3) is basically the prototype of the component itself. So if you started
at (1) with a custom array. At (3) the browser would be querying
`Array.prototype` i.e the basic foundational array object.
This explains why the full-name of certain methods on the global objects,
includes reference to 'prototype', for instance:
- `toFixed``Number.prototype.toFixed()`
- `.trim``String.prototype.trim()`.
### Using the function-object of `Object` to create an object literal
As a side-effect of uncovering the prototypical basis of `Object()` we can use
it to create an object literal in another way.
Standardly to create a new, empty object literal we would use:
```jsx
var slayerMembers = {...}
```
But now we could just as easily direct the code to `Object()` prototype and
achieve the same outcome:
```jsx
let slayerMembers = new Object();
```
### Finding the `[[Prototype]]` of a JS object with `getPrototypeOf()`
We can find out the `[[prototype]]` of a given object in JavaScript with a
special method applied to the object in question. The method we use is one of
the inherent methods that belong to the `Object()` constructor function:
`getPrototypeOf()` .
Here is an example:
```jsx
console.log(Object.getPrototypeOf(slayer));
/*
Returns:
Object {constructor: , __defineGetter__: ,
__defineSetter__: , hasOwnProperty: , __lookupGetter__: , …}
*/
```
We could also use the following approach to get the same output however this is
not recommended for production:
```jsx
console.log(slayer.__prototype__);
```
The output we get when querying the prototype is the same in each instance.
## But why bother using prototypes in your code?
It is informative to learn how prototypes are behind the object behaviour we
observe at a high level in JavaScript and to know that there is a common,
lower-level root but when would you actually need to use prototype methods?
The reason is mainly to do with performance and optimisation
> When we create a `deicide` and `slayer` object using the constructor function
> `Band()` JS creates two _copies_ of the constructor function, one for each
> generated object. This means there are two instances of `mainAlbum` for each
> object even though each do exactly the same thing. This wastes memory and
> performance time every time the script runs.
As a prototype object is an object, we can attach properties and methods to the
prototype object. Thus, enabling all the objects created using the constructor
function to share those properties and methods. However, the beauty is that the
method is applied to the prototype, so it is only stored in the memory once, but
every instance of the object has access to it.
```jsx
/* So say I wanted to add an age property to the Band()constructor that all objects of this type have access to. I could do this without wasting memory and without the need to change the original constructor function by referring to the prototype: */
Band.prototype.age = null;
Slayer.age = 25;
```
## Further steps
We are going to stop here for the time being since the primary aim of uncovering
prototypes and understanding how they underly all objects and object methods in
JS has been achieved.
However it is important to note that there is more that you can do with
prototypes. The main additional thing is using the prototypical basis of
constructor functions (i.e pseudo-classes) to add and remove key value pairs in
the resulting objects. This is useful when you want to create behaviour that is
much more like classes in compiled OOP languages.