Please forget all the object-oriented knowledge you learned before. Just consider the racing situation here. Yes, it's racing.
Recently I'm watching 24 Hours of Le Mans, a popular event in France. The fastest car is called the Le Mans prototype. Although these cars are made by manufacturers such as "Audi" or "Peugeot", they are not the kind of cars you see on the street or on the highway. They are made specifically for high-speed endurance events.
The manufacturer invests huge amounts of money to develop, design and manufacture these prototype cars, and engineers always try to make this project to the extreme. They conducted various experiments on alloys, biofuels, braking technology, compound composition and safety characteristics of tires. Over time, some of the techniques in these experiments have been repeatedly improved and entered the mainstream product line of vehicles. Some of the technology in the vehicle you are driving may have been debuted on the racing prototype.
You can also say that these mainstream vehicles inherit the technical prototypes from racing cars.
Until now, we have the basis for discussing the prototype and inheritance issues in JavaScript. While it is not as good as the classic inheritance pattern you know in C++, Java, or C#, it is just as powerful and potentially more flexible.
JavaScript is full of objects, which refers to objects in the traditional sense, that is, "a single entity that contains state and behavior." For example, an array in JavaScript is an object that contains several values and contains push, reverse, and pop methods.
var myArray = [1, 2];myArray.push(3);myArray.reverse();myArray.pop();var length = myArray.length;
Now the question is, where does push come from? The static languages we mentioned earlier use "class syntax" to define the structure of objects, but JavaScript is a language without "class syntax" and cannot define each array object using the syntax of Array "class". And because JavaScript is a dynamic language, we can place methods on objects as we actually need them. For example, the following code defines a point object used to represent a point in two-dimensional space, and also defines an add method.
var point = { x : 10, y : 5, add: function(otherPoint) { this.x += otherPoint.x; this.y += otherPoint.y; }};However, the above practices are not very scalable. We need to make sure that each point object contains an add method, and we also want all point objects to share the implementation of the same add method, instead of manually adding this method to each point object. This is where the prototype comes into play.
In JavaScript, each object remains hidden - a reference to another object, also known as a prototype. The array we created before references a prototype object, and so does the point objects we created ourselves. As mentioned above, prototype references are hidden, but there are also implementations of ECMAScript (the official name of JavaScript) that can access this prototype reference through the __proto__ attribute of an object (such as Google Chrome). Conceptually, we can treat objects as relationships similar to those represented in Figure 1-prototype.
Figure 1
Looking ahead, developers will be able to use the Object.getPrototypeOf function instead of the __proto__ attribute to obtain references to the object prototype. At the time of writing this article, the Object.getPrototypeOf function can already be used in Google Chrome, FIrefox and IE9 browsers. More browsers will implement this feature in the future because it is already part of the ECMAScript standard. We can use the following code to prove that the myArray and dot objects we created refer to two different prototype objects.
For the rest of this article, I will cross-use the __proto__ and Object.getPrototypeOf functions, mainly because __proto__ is easier to identify in graphs and sentences. It should be remembered that it (__proto__) is not the standard, and the Object.getPrototypeOf function is the recommended method to view object prototypes.
What makes the prototype so special?
We have not answered this question: Where does push in an array come from? The answer is: it comes from the myArray prototype object. Figure 2 is a screenshot of the script debugger in Chrome. We have called the Object.getPrototypeOf method to view the prototype object of myArray.
Figure 2
Note that there are many methods in myArray's prototype object, including those called push, pop, and reverse methods in code examples. Therefore, the push method does include the prototype object, but how does the myArray method refer to it?
myArray.push(3);
The first step to understanding how it works is to realize that the prototype is not special. The prototype is just a normal object. You can add methods, properties to the prototype and treat them as other JavaScript objects. However, to apply the statement of "pig" in George Orwell's novel "Animal Farm" - all objects should be equal, but some objects (those who follow the rules) are more equal than others.
Prototype objects in JavaScript are indeed special because they follow the following rules. When we tell JavaScript that we want to call an object's push method, or read the object's x property, the runtime will first look for the object itself. If the runtime cannot find what it wants, it follows the __proto__ reference and object prototype to look for the member. When we call myArray's push method, JavaScript does not find the push method on the myArray object, but on the prototype object of myArray, so JavaScript calls this method (see Figure 3).
Figure 3
The behavior described above refers to an object itself inheriting any method or property on the prototype. In JavaScript, inheritance is actually achieved without using class syntax. Just like a car that inherits corresponding technology from a racing prototype, a JavaScript object can also inherit functional features from a prototype object.
Figure 3 also shows that each array object can also maintain its own state and members. When requesting the length attribute of myArray, JavaScript will obtain the value of the length attribute in myArray without reading the corresponding value in the prototype. We can "overwrite" the push method by adding a method like push to the object. This will effectively hide the push method implementation in the prototype.
The real magic of prototypes in JavaScript is how multiple objects maintain references to the same prototype object. For example, if we create two arrays like this:
var myArray = [1, 2];var yourArray = [4, 5, 6];
Then these two arrays will share the same prototype object, and the following code evaluates to true:
Object.getPrototypeOf(myArray) === Object.getPrototypeOf(yourArray);
If we refer to the push method on two array objects, JavaScript will look for the push method shared on the prototype.
Figure 4
Prototype objects in JavaScript provide inheritance functions, and at the same time, the sharing of this method is implemented. The prototype is also chained. In other words, because a prototype object is just an object, one prototype object can be maintained to a reference to another prototype object. If you revisit Figure 2, you can see that the __proto__ property of the prototype is a non-null value pointing to another prototype. When JavaScript looks for members like the push method, it checks each object along the prototype reference chain until it is found, or reaches the end of the prototype chain. Prototype chains open up a flexible path for inheritance and sharing.
The next question you might ask is: How do I set up prototype references to those custom objects? For example, the point object used earlier, how can I add the add method to the prototype object and inherit the method from multiple point objects? Before answering this question, we need to look at the functions.
Functions in JavaScript are also objects. Such a statement brings several important results, and we will not cover all matters in this article. Among them, the ability to assign a function to a variable and pass a function as a parameter to another function constitutes the basic paradigm of modern JavaScript programming expression.
What we need to pay attention to is that the function itself is an object, so the function can have its own methods, properties, and reference a prototype object. Let's discuss the meaning of the following code.
// This will return true: typeof (Array) === "function"// Such expression is also: Object.getPrototypeOf(Array) === Object.getPrototypeOf(function () { })// Such expression is the same: Array.prototype != nullThe first line in the code proves that the array in JavaScript is a function. We will see how to call the Array function to create a new array object. The next line of code proves that the Array object uses the same prototype as any other function object, just like we see that the same prototype is shared between array objects. The last line of code proves that the Array function has a prototype property, and this prototype property points to a valid object. This prototype property is very important.
Each function object in JavaScript has a prototype property. Never confuse the __proto__ attribute of this prototype property. They are of different purpose, nor do they point to the same object.
// Return trueObject.getPrototypeOf(Array) != Array.prototype
Array.__proto__ provides an array prototype. Please treat it as an object inherited by the Array function.
Array.protoype provides prototype objects for all arrays. That is to say, it provides prototype objects of array objects like myArray, and also contains methods that all arrays will inherit. We can write some code to prove this fact.
// trueArray.prototype == Object.getPrototypeOf(myArray)// It is also trueArray.prototype == Object.getPrototypeOf(yourArray);
We can also use this new knowledge to repaint the previous diagram.
Figure 5
Based on what you know, imagine the process of creating a new object and making the new object behave like an array. One way is to use the following code.
// Create a new empty object var o = {};// Inherited from the same prototype, an array object o.__proto__ = Array.prototype;// Now we can call any method of the array...o.push(3);Although this code is interesting and works, the problem is that not every JavaScript environment supports writable __proto__ object properties. Fortunately, JavaScript does have a standard mechanism for creating objects. It only requires one operator to create new objects, and set the __proto__ reference of the new object, that is the "new" operator.
var o = new Array();o.push(3);
The new operator in JavaScript has three basic tasks. First, it creates a new empty object. Next, it will set the __proto__ property of the new object to match the prototype properties of the called function. Finally, the operator calls the function, passing the new object as a "this" reference. If you want to extend the last two lines of code, it will become the following situation:
var o = {};o.__proto__ = Array.prototype;Array.call(o);o.push(3);The function's call method allows you to specify the object referenced by "this" inside the function when calling the function. Of course, the author of the function needs to implement such a function in this case. Once the author creates such a function, it can be called a constructor.
Constructor
Constructors are the same as ordinary functions, but have the following two special properties.
Array is an example of a constructor. The Array function needs to be used with the new operator, and the initial letter of Array is capitalized. JavaScript includes Array as a built-in function, and anyone can write their own constructor. In fact, we can finally write a constructor for the previously created point objects.
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, which has the x and y attributes and an add method. You can imagine the final result as shown in Figure 6.
Figure 6
The problem now is that there is still a separate add method in each of our point objects. Using the prototype and inheritance we learned, we would rather transfer the add method of the point object from each point instance into Point.prototype. To achieve the effect of inheriting the add method, all we need to do is to modify 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);The mission is done! We just completed the inheritance mode of the prototype in JavaScript!
Figure 7
Summarize
I hope this article will help you uncover the mystery of JavaScript prototype concepts. What I saw at first was how the prototype allowed an object to inherit functions from other objects, and then saw how to combine the new operator and constructor to build the object. What is mentioned here is just the first step to unlocking the power and flexibility of the object prototype. This article encourages you to discover and learn new information about prototypes and JavaScript languages for yourself.
Also, please drive carefully. You will never know what (flawed) technology these vehicles traveling on the road will inherit from their prototypes.
Original link: Script Junkie Translation: Bole Online - Emje