1. Foreword This article is translated from Microsoft's master Scott Allen Prototypes and Inheritance in JavaScript. This article provides a detailed analysis and explanation of what Prototype is and why inheritance can be achieved through Prototype. It is one of the best works for understanding JS OO. If the translation is not good, please correct it and add it.
2. Text Object-oriented in JavaScript is different from other languages. It is best to forget the object-oriented concepts you are familiar with before learning. OO in JS is more powerful, more argumentative, and more flexible.
1. Classes and objects
JS is an object-oriented language from a traditional perspective. Properties and behaviors are combined into an object. For example, an array in JS is an object composed of properties and methods (such as push, reverse, pop, etc.).
var myArray = [1, 2];
myArray.push(3);
myArray.reverse();
myArray.pop();
var length = myArray.length;
The problem is: these methods ( Where does push) come from? Some static languages (such as JAVA) use classes to define the structure of an object. But JS is a language without "class" (classless). There is no class called "Array" that defines these methods for each array to inherit. Because JS is dynamic, we can add methods to the object at will when needed. For example, the following code defines an object that represents coordinates in a two-dimensional space and has an add method in it.
var point = {
x : 10,
y : 5,
add: function(otherPoint)
{
this.x = otherPoint.x;
this.y = otherPoint.y;
}
};
We want every point object to have an add method. We also hope that all point objects share an add method without having to add the add method to all point objects. This requires the prototype to appear.
2. About Prototypes
Every object in JS has an implicit attribute (state) - a reference to another object, called the prototype of the object. The array and point we created above Of course, they also contain references to their respective prototypes. The prototype reference is implicit, but it is what ECMAScript has implemented, allowing us to obtain it using the object's _proto_ (in Chrome) property. From a conceptual understanding, we can think of the relationship between objects and prototypes as shown in the figure below:
As developers, we will use the Object.getPrototypeOf function instead of the _proto_ attribute to view the prototype reference of the object. At the time of writing this article, the Object.getPrototypeOf function is supported in Chrome, Firefox, and IE9. In the future, more browsers will support this feature, which is already one of the ECMAScript standards. We can use the following code to prove that myArray and the point object we created before do indeed reference two different prototype objects.
Object.getPrototypeOf(point) != Object .getPrototypeOf(myArray);
In the rest of the article, I will also use _proto_, mainly because _proto_ is more intuitive in diagrams and sentences. But remember that this is not canonical, Object.getPrototypeOf is the recommended method for getting the prototype of an object.
2.1 What makes Prototypes so special?
We already know that the push method of array comes from the prototype object of myArray. Figure 2 is a screenshot in Chrome. We call the Object.getPrototypeOf method to obtain the prototype object of myArray.
Figure 2
Notice that the prototype object of myArray contains many methods, such as push, pop and reverse, which we used in the beginning code of. The prototype object is the only owner of the push method, but how is this method called through myArray?
myArray.push(3);
To understand how it is implemented, the first step is to realize that Protytype is not special at all. Prototypes are objects. We can add methods and properties to these objects like any other JS object. But at the same time, Prototype is also a special object.
Prototype is special because of the following rules: when we notify JS that we want to call the push method on an object or read a certain property, the interpreter (runtime) first looks for the object itself method or property. If the interpreter does not find the method (or attribute), it will follow the _proto_ reference to find each member in the object's prototype. When we call the push method in myArray, JS does not find push in the myArray object, but finds push in the prototype object of myArray, that is, the push method is called (Figure 3).
Figure 3
The behavior I described is essentially that the object itself inherits all methods and properties from its prototype. We don't need to use classes to implement this inheritance relationship in JS. That is, a JS object inherits properties from its prototype.
Figure 3 also tells us that each array object can maintain its own state and members. If we need the length property of myArray, JS will find the length value from myArray without looking for it in the prototype. We can use this feature to "override" a method, that is, put the method that needs to be overridden (like push) into myArray's own object. This can effectively hide the push method in the prototype.
3. Sharing Prototype
The real magic of Prototype in JS is that multiple objects can reference the same prototype object. For example, we create two arrays:
var myArray = [ 1, 2];var yourArray = [4, 5, 6];
The two arrays will share the same prototype object, the following code will return true
Object.getPrototypeOf(myArray) === Object.getPrototypeOf(yourArray);
If we call the push method on two arrays, JS will call the push method in their common prototype.
Prototype objects give us this inheritance feature in JS, they also allow us to share method implementations. Prototypes are also chained. In other words, a prototype is an object, and a prototype object can also have a reference to another prototype object. As you can see from Figure 2, the _proto_ attribute of prototype is a non-null value and points to another prototype. When JS starts to look for member variables, such as the push method, it will check each of these prototype references. Object until this object is found or the end of the chain is reached. This chain method increases the flexibility of inheritance and sharing in JS.
Next you may ask: How do I set the prototype reference of a custom object? For example, for the object point we created before, how do we add an add method to the prototype object so that all point objects can inherit it? Before we answer this question, let’s first understand the functions in JS.
4. About Funciton
Functions are also objects in JS. Functions have many important features in JS, and we can’t list them all in this article. But things like assigning a function to a variable or using a function as a parameter of another function are very basic methods in today's JS programming.
What we need to pay attention to is: because the function is an object, it has methods, properties and a reference to a prototype object. Let’s discuss the meaning of the following code:
// this will return true:
typeof (Array) === "function"
// and so will this:
Object.getPrototypeOf(Array) === Object.getPrototypeOf(function () { })
// and this, too:
Array.prototype != null
The first line of code proves that Array is a function in JS. Later we will see how to call the Array function to create a new array object.
The second line of code proves that the Array object and the function object refer to the same prototype, just like we saw before that all array objects share a prototype.
The last line proves that the Array function has a prototype attribute. Do not confuse this prototype attribute with the _proto_ attribute. Their purpose of use and the objects they point to are different.
// true
Array.prototype == Object .getPrototypeOf(myArray)
// also true
Array.prototype == Object.getPrototypeOf(yourArray);
We redraw the previous picture with the new knowledge:
Figure 5
Now we have to create an array object. One of the methods is:
// create a new, empty object
var o = {};
// inherit from the same prototype as an array object
o.__proto__ = Array.prototype;
// now we can invoke any of the array methods ...
o.push(3);
Although the above code looks good, the problem is that not every JS environment supports the _proto_ attribute of the object. Fortunately, JS has a standard built-in mechanism for creating new objects and setting the object's _proto_ property. This is the "new" operator.
var o = new Array();
o .push(3);
The "new" operator has three important tasks in JS: First, it creates a new empty object. Next, it sets the _proto_ attribute of this new object to point to the calling function's prototype attribute. Finally, the calling function is executed and the "this" pointer points to the new object. If we expand the above two lines of code, we will get the following code:
var o = {};
o.__proto__ = Array.prototype;
Array.call(o);
o.push(3);
The "call" method of a function allows you to call a function and specify that "this" in the function points to the new object passed in. Of course, we also want to create our own objects through the above method to achieve object inheritance. This function is what we are familiar with - the constructor.
5. Constructor
The constructor is a common JS function object with two unique identifiers:
1. The first letter is capitalized (easy to identify).
2. Use new operator connection to construct a new object.
Array is a constructor - the Array function is connected with new and the first letter is capitalized. The Array function in JS is built-in, but anyone can create their own constructor. In fact, it's finally time to create a constructor for the point object.
var Point = function (x, y) {
this.x = x;
this.y = y;
this.add = function (otherPoint) {
this.x = otherPoint.x;
this.y = otherPoint .y;
}
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2);
In the above code, we use the new operator and the Point function to construct a point object. In memory you can think of the final result as shown in Figure 6.
Figure 6
The problem now is that the add method exists in every point object. Given what we know about prototypes, it would be a better choice to add the add method to Point.prototype (without having to copy the code for the add method into every object). In order to achieve this purpose, we need to make some modifications to the Point.prototype object.
var Point = function (x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function (otherPoint) {
this.x = otherPoint.x;
this.y = otherPoint.y;
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2) ;
Okay! We have implemented inheritance in JS using prototype!
6. Summary
I hope this article can help you clear up the fog of prototype. Of course, this is just an introduction to the powerful and flexible prototype. I hope readers can explore and discover more about prototypes on their own.