Prototype inheritance in JavaScript

Source: Internet
Author: User

In JavaScript object-oriented programming, prototype inheritance is not only a focus but also an easy point to master. In this article, we will explore some of the prototype inheritance in JavaScript.

Basic form

Let's start by looking at the following code:

<code>//constructor function  Shape () {    this.x = 0;    This.y = 0;} A shape instance   var s = new shape ();   </code> 

Although this example is very simple, there are four "very important" points that need to be clarified here:

1.s is an object, and by default it has access to Shape.prototype, which is the prototype owned by the object created by the shape constructor; In short, Shape.prototype is an object that "monitors" all of the shape instances. You can think of a prototype of an object as a fallback set of many attributes (variables/functions) that will be looked up in the prototype when it cannot find anything on its own.

2. The prototype can be shared across all the shape instances. For example, all prototypes have access to the prototype (directly).

3. When you invoke a function in an instance, the instance looks for the definition of the function on its own. If it is not found, then the prototype will look up the definition of the function.

4. Regardless of where the definition of the called function is found (either in the instance itself or in its prototype), the value of this is a pointer to the instance used to invoke the function. So if we call a function that is dry in s, if the function is not directly defined in S, but in S's prototype, the this value still points to S.

Now let's apply the points highlighted above in one example. Suppose we bind a function getposition () to S. We may do this:

<code>s.getposition () {    return [this.x,this.y];} </code> 

There is nothing wrong with doing so. You can call S.getposition directly () and then you will get the returned array.

But what if we create another instance of Shape S2, and can it still invoke the GetPosition () function?

The answer is obviously no.

The GetPosition function is created directly north of the instance S. Therefore, this function is not to be heard in the S2.

When you call S2.getposition (), the following steps occur sequentially (note that the third step is very important):

1. Example S2 will check the definition of getposition;
2. This function does not exist in the S2;
The prototype of 3.S2 (and the backup set shared with s) examines the definition of getposition;
4. This function does not exist with the prototype;
5. The definition of this function is not found;

A simple (but not optimal) solution is to redefine getposition in the instance S2 (and every subsequent instance that needs to be getposition). This is a bad practice because you are doing meaningless copy code work, and defining a function in each instance consumes more memory (if you care about that).

We have a better way.

Defining attributes in a prototype

We can fully achieve the purpose of sharing the GetPosition function for all instances, not defining getposition in each instance, but in the prototype of the constructor function. Let's look at the following code:

<code>//constructor function   Shape () {    this.x = 0;    This.y = 0;}  Shape.prototype.getPosition = function () {    return [this.x,this.y];  }  var s = new Shape (),        </code> 

Because prototypes are shared across all shape instances, s and S2 can access the GetPosition function.

Calling the S2.getposition () function goes through the following steps:

1. Example s2 Check the definition of getposition;
2. The function does not exist with the S2;
3. Check the prototype;
The definition of 4.getPosition exists in the prototype;
5.getPosition will be executed together with this point pointing to S2;

Properties that are bound to prototypes are well suited for reuse. You can reuse the same function in all instances.

A trap in a prototype

Be very careful when you bind objects or arrays to prototypes. All instances will share references to these bound objects/arrays. If an instance manipulates an object or array, all instances will be affected.

<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.types = [' round ', ' flat '];s = new shape (); s2 = new shape (); S.types.push (' bumpy '); Console.log (s.types); [' Round ', ' flat ', ' bumpy ']console.log (s2.types); [' Round ', ' flat ', ' bumpy ']    </code> 

When the line of S.types.push (' bumpy ') runs, the instance s will check for an array called types. It does not exist with the instance s, so the prototype examines this array. This array, types, exists in the prototype, so we add an element ' bumpy ' to him.

As a result, because S2 also shares the prototype, it can also find that the types array has changed in an indirect way.

Similar things happen in the real world when you use Backbone.js. When you define a view/model/collection, Backbone adds the attributes you pass through the Extend function (for example: Backbone.View.extend ({})) to the prototype of the entity you define.

This means that if you add an object or an array when you define an entity, all instances will share those objects or arrays, and it is likely that one of your instances will ruin another instance. In order to avoid this, you will often see dreams include these objects/arrays in a function, returning an instance of an object/array each time:

Note: Backbone this point in the Model Defaults section:

Remember that in JavaScript, objects are passed in a reference way, so if you include an object as the default, it will be shared across all instances. Therefore, we define defaults as a function.

Another type of shape

Suppose now we want to create a particular type of shape, say a circle. It would be nice if it could inherit all the features of shape and also define custom functions in its prototype:

<code>function Shape () {  this.x = 0;  This.y = 0;} function Circle () {  This.radius = 0;}   </code> 

So how do we describe a circle as a shape? There are several ways to do this:

1. Borrowing constructors and assigning values to prototypes

When we create a circle, we want the instance to have a radius (from the circle constructor), and an x position, a Y position (from the shape constructor).

We just declare C = new Circle (), then C is only a radius. The shape constructor initializes x and Y. We want this feature. So let's borrow this feature.

<code>function Circle () {  This.radius = 0;  Shape.call (this);}   </code> 

The last line of code, Shape.call (this), invokes the shape constructor and changes the this value that points to this when the circle constructor is invoked. What is this about?

Now let's use the constructor above to create a new circle and see what happens:

<code>var c = new Circle ();    </code> 

This line of code calls the Circle constructor, which first bindings a variable radius in C. Remember, this time this is pointing to C. We then call the shape constructor and then point the this value in shape to the current this value in circle, which is C. The shape constructor binds x and Y to the current this, which means that C now has an X and Y property with a value of 0.

In addition, it is not important for you to place Shape.call (this) in this example. If you want to reload X and Y after initialization (that is, place the center point in a different location), you can do this after calling the shape function.

The problem is that the circle we instantiate now has the variable x, y, and radius, but it doesn't get anything from the shape's prototype. We need to set up the circle constructor to reuse the shape's prototype as its prototype-so that all the circles can get the benefits of shape.

One way is for us to set the value of Circle.prototype to Shape.prototype:

<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;  Shape.call (this);} Circle.prototype = Shape.prototype;var s = new Shape (),    C = new Circle ();   </code> 

This works fine, but it's not the best choice. Instance c now has permission to access the GetPosition function because the Circle constructor function and the shape constructor function share its prototype.

What if we still want to define a Getarea function for all the meta-elements? We will bind this function to the prototype of the Circle constructor function so that it can be used for all circles.

Write the following code:

<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;  Shape.call (this);} Circle.prototype = Shape.prototype; Circle.prototype.getArea = function () {  return Math.PI * This.radius * this.radius;}; var s = new Shape (),    C = new Circle ();  </code> 

Now that the circle and shape share the same prototype, we added a function in the Circle.prototype, which is actually the equivalent of adding a function to the shape.prototype.

How can this look like this!

An instance of a shape does not have a RADIUS variable, only the circle instance has a RADIUS variable. But now all of the shape instances have access to the Getarea function-which causes an error, but everything is fine when all the circles call this function.

Setting all the prototypes to the same object does not meet our needs.

The 2.Circle prototype is an example of a shape
<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;} Circle.prototype = new Shape (); var c = new Circle ();   </code> 

This method is very cool. We did not borrow the constructor function but circle has x and Y, and we also have the GetPosition function. How does it come true?

Circle.prototype is now an example of a shape. This means that C has a direct variable radius (provided by the Circle constructor function). However, in the prototype of C, there is an X and Y. Now notice that something interesting is coming: in the prototype of C, there is a definition of the getposition function. It seems to be the case:

So if you try to get c.x, it will be found in the prototype of C.

The disadvantage of this approach is that if you want to overload x and Y, you have to do it in the circle constructor or circle prototype.

<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;} Circle.prototype = new Shape (); circle.prototype.x = 5; Circle.prototype.y = 10;var c = new Circle (); Console.log (C.getposition ()); [5, ten]   </code> 

Calling C.getposition will go through the following steps:

1. The function is not found in C;
2. The function is not found in the prototype of C (example of shape);
3. The function is found in the prototype of the shape instance (prototype of c);
4. The function is called along with the this that points to C;
5. In the definition of the GetPosition function, we look for x in this.
6.x is not found directly in C;
7. We look for X in the prototype of C (shape instance);
8. We found X in the prototype of C;
9. We find y in the prototype of C;

In addition to having a layer of prototype chain caused by the headache, this method is very good.

This method can also be substituted with object.create ().

3. Borrow the constructor and use the Object.create
<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;  Shape.call (this);  this.x = 5;  This.y = 10;} Circle.prototype = Object.create (Shape.prototype); var c = new Circle (); Console.log (C.getposition ()); [5, ten]   </code> 

One of the great benefits of this approach is that X and Y are directly bound to C-this will make the query much faster (if your program cares about it) because you no longer need to query the prototype chain upwards.

Let's take a look at the alternative method of Object.create (Polyfill):

<code>object.create = (function () {  //Intermediate constructor function  F () {}  return function (o) {    ...    Set the prototype of the intermediate constructor to the object we gave it o    f.prototype = o;    Returns an instance of an intermediate constructor;/        /It is an empty object but the prototype is the object we give it to O    return new F ();}  ;}) ();       </code> 

The process is basically finished circle.prototype = new Shape (); just now Circle.prototype is an empty object (an instance of an intermediate constructor F), and its prototype is shape.prototype.

Which method should you use

It is important to remember that if you have an object/array bound to the shape constructor, then all the circles can modify these shared objects/arrays. This method can be a major drawback if you set Circle.prototype to an instance of shape.

<code>function Shape () {  this.x = 0;  This.y = 0;  This.types = [' flat ', ' round '];} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;} Circle.prototype = new Shape (), var c = new Circle (),    c2 = New Circle (), C.types.push (' bumpy '), Console.log (c.types); c8/>//["Flat", "round", "Bumpy"]console.log (c2.types); ["Flat", "round", "bumpy"]   </code> 

To avoid this, you can borrow the shape's constructor and use object.create so that each circle can have its own types array.

<code> ...function Circle () {  This.radius = 0;  Shape.call (this);} Circle.prototype = Object.create (Shape.prototype), var c = new Circle (),    c2 = new Circle (); C.types.push (' bumpy '); Console.log (c.types);  ["Flat", "round", "Bumpy"]console.log (c2.types); ["Flat", "round"]     </code> 
A more advanced example

We are now going further on the basis of the discussion above, creating a new circle type, Sphere. An ellipse is about the same as a circle, except that there are different formulas for calculating the area.

<code>function Shape () {  this.x = 0;  This.y = 0;} Shape.prototype.getPosition = function () {  return [this.x, This.y];}; function Circle () {  This.radius = 0;  Shape.call (this);  this.x = 5;  This.y = 10;} Circle.prototype = Object.create (Shape.prototype); Circle.prototype.getArea = function () {  return Math.PI * This.radius * this.radius;}; function Sphere () {}//TODO: Set the prototype chain here Sphere.prototype.getArea = function () {  return 4 * Math.PI * This.radius * This . Radius;}; var sp = new Sphere ();    </code> 

Which method should we use to set up the prototype chain? Remember, we don't want to ruin our definition of a circle's getarea. We just want to have another way of implementing the ellipse.

We can also borrow the constructor and assign a value to the prototype (Method 1). Because doing so will change the definition of the getarea of all circles. However, we can use object.create or set the sphere prototype as an instance of circle. Let's see what we should do:

<code> ...function Circle () {  This.radius = 0;  Shape.call (this);  this.x = 5;  This.y = 10;} Circle.prototype = Object.create (Shape.prototype); Circle.prototype.getArea = function () {  return Math.PI * This.radius * this.radius;}; function Sphere () {  circle.call (this);} Sphere.prototype = Object.create (Circle.prototype); Sphere.prototype.getArea = function () {  return 4 * Math.PI * This.radius * this.radius;}; var sp = new Sphere ();    </code> 

Calling Sp.getarea () will go through the steps:

1. Find the definition of getarea in SP;
2. No relevant definitions were found in SP;
3. Find in the prototype of sphere (an intermediate object, its prototype is circle.prototype);
4. The definition of Getarea is found in this intermediate object, since we have redefined the Getarea in the sphere prototype, where the new definition is used;
5. Together with the this call to the SP Getarea method;

We note that Circle.prototype also has a definition of getarea. However, since Sphere.prototype already has a getarea definition, we will never use the getarea-in Circle.prototype so that we can "reload" the success This function (overloaded one that defines a function with the same name in front of the query chain).

Prototype inheritance in JavaScript

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.