8

JavaScript Prototype Chains and Inheritance | Bits and Pieces

 2 years ago
source link: https://blog.bitsrc.io/javascript-prototype-chains-and-inheritance-382ce1c0f3f3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JavaScript Prototype Chains and Inheritance

Excerpt from JavaScript advanced programming

Inheritance is one of the most talked about concepts in OO languages.

Many OO languages support two inheritance methods: interface inheritance and implementation inheritance. Interface inheritance only inherits method signatures, while implementation inheritance inherits actual methods.

The method has no signature, and interface inheritance cannot be implemented in ECMAScript. ECMAScript only supports implementation inheritance, and its implementation inheritance is mainly implemented by relying on the prototype chain.

1*_9df3qnJdIqZ3ZlVjUALhQ.png?q=20
javascript-prototype-chains-and-inheritance-382ce1c0f3f3

The relationship between Constructors, Prototypes and Instances

Each constructor (constructor) has a prototype object (prototype), the prototype object contains a pointer to the constructor, and the instance (instance) contains an internal pointer to the prototype object.

If you let the prototype object point to an instance of another type… interesting things happen. For example: constructor1.prototype = instance2
Given that the above game rules are in effect, if you try to reference a property p1 of instance1 constructed by constructor1:

  • First, it will be found in the internal properties of instance1;
  • Then it will look for instance1.__proto__(constructor1.prototype), and constructor1.prototype is actually instance2, that is to say, find the property p1 in instance2;
  • If there is still no instance in instance2, the program will not be discouraged at this time, it will continue to search in instance2.__proto__(constructor2.prototype), until the prototype object of Object.
Search track: instance1--> instance2 --> constructor2.prototype…-->Object.prototype

The trajectory of this search looks like a long chain, and because the prototype acts as a link in the rules of the game, we call this chain of instances and prototypes the prototype chain.

Here is an example:

The instance finds the getFatherValue method in the Father prototype through the prototype chain.

The relationship between Prototype and Instance

After using the prototype chain, how do we judge the inheritance relationship between the prototype and the instance?

There are generally two methods.

The first is to use the instanceof operator, as long as you use this operator to test the instance and the constructor that appears in the prototype chain, the result will return true. The following lines of code illustrate this.

console.log(instance instanceof Object);//true
console.log(instance instanceof Father);//true
console.log(instance instanceof Son);//true

Due to the prototype chain, we can say that instance is an instance of any of the types Object, Father or Son. Therefore, the results of all three constructors return true.

The second is to use the isPrototypeOf() method, also as long as it is a prototype that has appeared in the prototype chain, the isPrototypeOf() method will return true, as shown below.

alert(Object.prototype.isPrototypeOf(instance));//true
alert(Father.prototype.isPrototypeOf(instance));//true
alert(Son.prototype.isPrototypeOf(instance));//true

Prototype chain problems

The prototype chain is not perfect, it contains the following two problems.

  • When the prototype chain contains a prototype of a reference type value, the reference type value will be shared by all instances;
  • When creating a subtype (such as creating an instance of Son), you cannot pass parameters into the constructor of a supertype (such as Father).

Prototype chain alone is rarely used in practice, and for this reason, there are some attempts to make up for the inadequacy of prototype chain.

Borrowed constructor

To solve the above two problems in the prototype chain, we started to use a technique called constructor stealing (also called classical inheritance).

The basic idea is to call the supertype constructor inside the subtype constructor.

Obviously, borrowing constructors solves two major problems with prototype chains in one fell swoop:

  • Ensures the independence of reference type values in the prototype chain and is no longer shared by all instances
  • Subtypes can also pass parameters to supertypes when they are created

It follows that if you just borrow the constructor, you will not be able to avoid the problems with the constructor pattern — methods are defined in the constructor, so function reuse is not available. And supertypes (such as Father) Methods defined in , are also invisible to subtypes. Considering this, the technique of borrowing constructors is also rarely used alone.

Composition inheritance

Combinatorial inheritance, sometimes called pseudo-classical inheritance, refers to a pattern of inheritance that combines the techniques of prototype chaining and borrowing constructors to make use of the best of both worlds.

Basic idea: Use the prototype chain to implement the inheritance of prototype properties and methods, and implement the inheritance of instance properties by borrowing constructors.

In this way, function reuse is achieved by defining methods on the prototype, while ensuring that each instance has its own properties. As shown below.

Combinatorial inheritance avoids the pitfalls of prototype chains and borrowed constructors, and combines their advantages to become the most commonly used inheritance pattern in JavaScript.

Moreover, instanceof and isPrototypeOf( ) can also be used to identify objects created based on composite inheritance.

At the same time, we also noticed that combined inheritance actually calls the parent class constructor twice, causing unnecessary consumption, so how to avoid this unnecessary consumption, we will talk about this later.

prototypal inheritance

This method was originally proposed by Douglas Crockford in a 2006 article titled “Prototypal Inheritance in JavaScript” (Prototypal Inheritance in JavaScript). His idea is that with the help of prototypes, new objects can be created based on existing objects. object, without having to create a custom type for it. The general idea is as follows:

Inside the object() function, a temporary constructor is created first, then the incoming object is used as the prototype of the constructor, and finally a new instance of the temporary type is returned.function object(o){
function F(){}
F.prototype = o;
return new F();
}

Essentially, object() returns a new object that references the passed in object. This can lead to some shared data issues, as follows.

var person = {
friends : ["Van","Louis","Nick"]
};
var anotherPerson = object(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"

In this example, the person object that can be used as the basis for another object, so we pass it into the object() function, which then returns a new object.

This new object has person as a prototype, so its The prototype contains reference type value properties. This means that person.friends is not only owned by person, but also shared by anotherPerson and yetAnotherPerson.

In ECMAScript 5, the above prototypal inheritance is normalized by adding the object.create() method.

object.create() accepts two parameters:

  • An object to use as the prototype of the new object
  • (optional) an object that defines additional properties for the new object
var person = {
friends : ["Van","Louis","Nick"]
};
var anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"

When object.create() has only one parameter, the function is the same as the above object method, and its second parameter has the same format as the second parameter of the Object.defineProperties() method: each property is defined by its own descriptor.

Any property specified in this way overrides the property of the same name on the prototype object.

For example:

var person = {
name : "Van"
};
var anotherPerson = Object.create(person, {
name : {
value : "Louis"
}
});
alert(anotherPerson.name);//"Louis"

Reminder: In prototypal inheritance, properties containing reference-type values always share the corresponding value, just like using the prototype pattern.

parasitic inheritance

Parasitic inheritance is a line of thought closely related to prototypal inheritance, which is also generalized by Crockford.

The idea of parasitic inheritance is similar to the (parasitic) constructor and factory pattern, i.e. create a function that just encapsulates the inheritance process, that function internally enhances the object in some way, and finally looks like it really does everything Works the same as returning an object.

See below:

function createAnother(original){
var clone = object(original);//Create a new object by calling the object function
clone.sayHi = function(){
alert("hi");
};
return clone;
}

The code in this example returns a new object based on personanotherPerson . The new object not only has all the properties and methods of person , but is also enhanced to have a sayH() method.

Note: Using parasitic inheritance to add functions to objects will reduce efficiency due to the inability to reuse functions; this is similar to the constructor pattern.

Parasitic Compositional Inheritance

As mentioned earlier, compositional inheritance is the most commonly used inheritance pattern in JavaScript; however, it also has its own shortcomings.

The biggest problem with compositional inheritance is that in any case, the parent class constructor will be called twice: once to create the subtype prototype time, and another time inside the subtype constructor.

Parasitic compositional inheritance is there to reduce the overhead of calling the superclass constructor.

The basic idea behind it is: you don’t have to call the supertype’s constructor in order to specify the subtype’s prototype

function extend(subClass,superClass){
var prototype = object(superClass.prototype);
prototype.constructor = subClass;
subClass.prototype = prototype;
}

The efficiency of extend is reflected in that it does not call the superClass constructor, so it avoids creating unnecessary and redundant properties on subClass.prototype. At the same time, the prototype chain can remain unchanged; therefore, instanceof and isPrototypeOf( ) method.

Above, parasitic composite inheritance, which combines the advantages of parasitic inheritance and composite inheritance, is the most effective way to realize type-based inheritance.

Let’s look at another more efficient extension of extend:

What I don’t quite understand is why “new F()”, since the purpose of extend is to point the prototype of the subtype to the prototype of the supertype, why not do the following directly?

subClass.prototype = superClass.prototype;

Obviously, based on the above operations, the subtype prototype will be shared with the supertype prototype, and there is no inheritance relationship at all.

The new operator

In order to trace the source, I studied what the new operator does exactly? I found that it was actually very simple, and it did three things.

var obj  = {};
obj.__proto__ = F.prototype;
F.call(obj);
  • We created an empty object obj;
  • We point the __proto__ member of this empty object to the F function object prototype member object;
  • We replace the this pointer of the F function object with obj, and then call the F function.

We can understand it this way: when the constructor is called with the new operator, the following changes actually happen inside the function:

1.Create an empty object, and the this variable refers to the object, and also inherits the prototype of the function.
2. Properties and methods are added to the object referenced by this.
3. The newly created object is referenced by this, and finally returns this implicitly.

The __proto__ property is the key to specifying the prototype

Above, the parent class is inherited by setting the __proto__ attribute. If the new operation is removed, directly refer to the following writing method.

subClass.prototype = superClass.prototype;//prototype

Then, when using the instanceof method to determine whether the object is an instance of the constructor, there will be confusion.

If the reference is written as above, then the extend code should be

function extend(subClass, superClass) {
subClass.prototype = superClass.prototype;subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
}

At this point, look at the following test:

function a(){}
function b(){}
extend(b,a);
var c = new a(){};
console.log(c instanceof a);//true
console.log(c instanceof b);//true

It is understandable and correct that c is considered to be an instance of a; but c is also considered to be an instance of b, which is wrong. The reason for this is that the instanceof operator should compare c.__proto__ with the constructor.prototype (ie b.prototype or a.prototype) are both equal, and extend(b,a); then b.prototype === a.prototype, so the above unreasonable output is printed.

So in the end, prototype chain inheritance can be implemented like this, for example:

Extension

After using the prototype chain, when looking for a property of an object, JavaScript will traverse up the prototype chain until it finds a property with a given name, until the search reaches the top of the prototype chain — which is Object.prototype — but still does not find the specified If you want to avoid the prototype chain lookup, it is recommended to use the hasOwnProperty method.

Because hasOwnProperty is the only function in JavaScript that handles properties but does not look up the prototype chain.

For example:

console.log(instance1.hasOwnProperty('age'));//true

Thank you for reading. I hope you have found this refresher on prototypal inheritance useful.

Build component-driven. It’s better, faster, and more scalable.

Forget about monolithic apps, start building component-driven software. Build better software from independent components and compose them into infinite features and apps.

OSS Tools like Bit offer a great developer experience for building component-driven. Start small and scale with many apps, design systems or even Micro Frontends. Give it a try →

0*sl8gxAMrIiX2WaMw?q=20
javascript-prototype-chains-and-inheritance-382ce1c0f3f3
An independent product component: watch the auto-generated dependency graph

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK