Home > Article > Web Front-end > In-depth exploration of javascript prototype is not as simple as prototype inheritance_javascript skills
The prototype attribute of an object in JavaScript can return a reference to the prototype of the object type. This is a rather confusing explanation. To understand it, you must first correctly understand the concepts of object type (Type) and prototype (prototype).
1 What is prototype
The prototype attribute of an object in JavaScript can return a reference to the prototype of the object type. This is a rather confusing explanation. To understand it, you must first correctly understand the concepts of object type (Type) and prototype (prototype).
We said earlier that there is a "creation" relationship between an object's class (Class) and an object instance (Instance). Therefore, we regard "class" as a modeling of object characteristics, and objects as classes. The embodiment of characteristics, or class (Class) is a type (Type) of an object. For example, in the previous example, the types of p1 and p2 are both Point. In JavaScript, this can be verified through the instanceof operator:
p1 instanceof Point
p2 instanceof Point
However, Point is not the only type of p1 and p2. Because p1 and p2 are both objects, Object is also their type. Because Object is a more general class than Point, we say that there is a derived relationship between Object and Point. We will know later that this relationship is called "inheritance". It is also a special case of the generalized relationship between objects and is an indispensable basic relationship in object-oriented.
In the object-oriented field, instance and type are not the only pair of describable abstract relationships. In JavaScript, another important abstract relationship is type (Type) and prototype (prototype). This relationship is a higher-level abstract relationship, which happens to form a three-layer chain with the abstract relationship between types and instances.
In real life, we often say that something is based on another thing. These two things can be of the same type or different types. The idiom "follow the gourd and draw the gourd", the gourd here is the prototype, and the gourd is the type. Using JavaScript's prototype to represent it is "ladle.prototype = a certain gourd" or "ladle.prototype = new gourd ()".
To deeply understand prototype, you can study one of its design patterns - prototype pattern. The core of this pattern is to use prototype instances to specify the types of objects to be created, and to create new objects by copying these prototypes. JavaScript prototype is similar to this method.
For details about prototype pattern, please refer to "Design Patterns" ("Design Patterns"), which is beyond the scope of this article.
Note that, unlike the relationship between types and instances, the relationship between prototypes and types requires that a type can only have one prototype at a time (and an instance can obviously have multiple types at a time). For JavaScript, this restriction has two meanings. The first is that each specific JavaScript type has and has only one prototype. By default, this prototype is an Object object (note that it is not an Object type!) . The second is that the type to which this object belongs must be a type chain that satisfies the prototype relationship. For example, the types of p1 are Point and Object, and an Object object is the prototype of Point. If there is an object whose types are ClassA, ClassB, ClassC and Object, then these four classes must form a complete prototype chain.
What’s interesting is that JavaScript does not specify the type of the prototype of a type (this is another very awkward statement), so it can be any type, usually some kind of object, so, object-type-prototype (object) It may form a ring structure, or other interesting topological structures. These structures bring a variety of uses to JavaScript, some of which are not only clever but also full of beauty. The following section mainly introduces the usage of prototype.
2 Tips on using prototype
Before understanding the skills on using prototype, you must first understand the characteristics of prototype. First of all, JavaScript provides a prototype attribute for each type (Type). Point this attribute to an object, and this object becomes the "prototype" of this type, which means that all objects created by this type have this Characteristics of the prototype. In addition, JavaScript objects are dynamic, and prototypes are no exception. Adding or subtracting attributes to prototype will change the prototype of this type. This change will directly affect all objects created by this prototype, for example:
<script> function Point(x,y) { this.x = x; this.y = y; } var p1 = new Point(1,2); var p2 = new Point(3,4); Point.prototype.z = 0; //动态为Point的原型添加了属性 alert(p1.z); alert(p2.z); //同时作用于Point类型创建的所有对象 </script>
If an attribute named a is added to the prototype of an object type, and the object itself has an attribute with the same name named a, then when accessing the attribute a of the object, the object The own properties "cover" the prototype properties, but the prototype properties do not disappear. When you use the delete operator to delete the property a of the object itself, the object's prototype properties regain visibility. Using this feature, you can set default values for the properties of the object, for example:
<script> function Point(x, y) { if(x) this.x = x; if(y) this.y = y; } Point.prototype.x = 0; Point.prototype.y = 0; var p1 = new Point; var p2 = new Point(1,2); </script>
The above example sets the default value (0,0) for the Point object through prototype, so the value of p1 is ( 0,0), the value of p2 is (1,2), and the value of p2 can be restored to (0,0) by delete p2.x, delete p2.y;. Here is a more interesting example:
<script> function classA() { this.a = 100; this.b = 200; this.c = 300; this.reset = function() { for(var each in this) { delete this[each]; } } } classA.prototype = new classA(); var a = new classA(); alert(a.a); a.a *= 2; a.b *= 2; a.c *= 2; alert(a.a); alert(a.b); alert(a.c); a.reset(); //调用reset方法将a的值恢复为默认值 alert(a.a); alert(a.b); alert(a.c); </script>
Using prototype, you can also set a read-only getter for the object's properties to prevent it from being overwritten. Here is an example:
<script> function Point(x, y) { if(x) this.x = x; if(y) this.y = y; } Point.prototype.x = 0; Point.prototype.y = 0; function LineSegment(p1, p2) { //私有成员 var m_firstPoint = p1; var m_lastPoint = p2; var m_width = { valueOf : function(){return Math.abs(p1.x - p2.x)}, toString : function(){return Math.abs(p1.x - p2.x)} } var m_height = { valueOf : function(){return Math.abs(p1.y - p2.y)}, toString : function(){return Math.abs(p1.y - p2.y)} } //getter this.getFirstPoint = function() { return m_firstPoint; } this.getLastPoint = function() { return m_lastPoint; } this.length = { valueOf : function(){return Math.sqrt(m_width*m_width + m_height*m_height)}, toString : function(){return Math.sqrt(m_width*m_width + m_height*m_height)} } } var p1 = new Point; var p2 = new Point(2,3); var line1 = new LineSegment(p1, p2); var lp = line1.getFirstPoint(); lp.x = 100; //不小心改写了lp的值,破坏了lp的原始值而且不可恢复 alert(line1.getFirstPoint().x); alert(line1.length); //就连line1.lenght都发生了改变 </script>
Rewrite this.getFirstPoint() as follows:
this.getFirstPoint = function()
{
function GETTER(){};
GETTER.prototype = m_firstPoint;
return new GETTER();
}
can avoid this problem and ensure the read-only nature of the m_firstPoint attribute.
<script> function Point(x, y) { if(x) this.x = x; if(y) this.y = y; } Point.prototype.x = 0; Point.prototype.y = 0; function LineSegment(p1, p2) { //私有成员 var m_firstPoint = p1; var m_lastPoint = p2; var m_width = { valueOf : function(){return Math.abs(p1.x - p2.x)}, toString : function(){return Math.abs(p1.x - p2.x)} } var m_height = { valueOf : function(){return Math.abs(p1.y - p2.y)}, toString : function(){return Math.abs(p1.y - p2.y)} } //getter this.getFirstPoint = function() { function GETTER(){}; GETTER.prototype = m_firstPoint; return new GETTER(); } this.getLastPoint = function() { function GETTER(){}; GETTER.prototype = m_lastPoint; return new GETTER(); } this.length = { valueOf : function(){return Math.sqrt(m_width*m_width + m_height*m_height)}, toString : function(){return Math.sqrt(m_width*m_width + m_height*m_height)} } } var p1 = new Point; var p2 = new Point(2,3); var line1 = new LineSegment(p1, p2); var lp = line1.getFirstPoint(); lp.x = 100; //不小心改写了lp的值,但是没有破坏原始的值 alert(line1.getFirstPoint().x); alert(line1.length); //line1.lenght不发生改变 </script>
In fact, setting an object as the prototype of a type is equivalent to creating a read-only copy of the object by instantiating this type. Changing the copy at any time will not affect the The original object, and changes to the original object will affect the copy, unless the changed properties have been overwritten by the copy's own properties of the same name. Use the delete operation to delete the object's own property with the same name, and you can restore the visibility of the prototype property. Here's another example:
<script> function Polygon() { var m_points = []; m_points = Array.apply(m_points, arguments); function GETTER(){}; GETTER.prototype = m_points[0]; this.firstPoint = new GETTER(); this.length = { valueOf : function(){return m_points.length}, toString : function(){return m_points.length} } this.add = function(){ m_points.push.apply(m_points, arguments); } this.getPoint = function(idx) { return m_points[idx]; } this.setPoint = function(idx, point) { if(m_points[idx] == null) { m_points[idx] = point; } else { m_points[idx].x = point.x; m_points[idx].y = point.y; } } } var p = new Polygon({x:1, y:2},{x:2, y:4},{x:2, y:6}); alert(p.length); alert(p.firstPoint.x); alert(p.firstPoint.y); p.firstPoint.x = 100; //不小心写了它的值 alert(p.getPoint(0).x); //不会影响到实际的私有成员 delete p.firstPoint.x; //恢复 alert(p.firstPoint.x); p.setPoint(0, {x:3,y:4}); //通过setter改写了实际的私有成员 alert(p.firstPoint.x); //getter的值发生了改变 alert(p.getPoint(0).x); </script>
Note that the above example shows that prototype can be used to quickly create multiple copies of an object. In general, using prototype to create a large number of complex objects is better than using other methods. Any way to copy objects is much faster. Notice that using one object as a prototype to create a large number of new objects is the essence of the prototype pattern.
The following is an example:
<script> var p1 = new Point(1,2); var points = []; var PointPrototype = function(){}; PointPrototype.prototype = p1; for(var i = 0; i < 10000; i++) { points[i] = new PointPrototype(); //由于PointPrototype的构造函数是空函数,因此它的构造要比直接构造//p1副本快得多。 } </script>
In addition to the usage techniques mentioned above, prototype has other uses because of its unique characteristics and is used as the most widely used and Perhaps the most well-known use of it is to simulate inheritance, which is discussed in the next section.
3 The essence of prototype
The role of prototype has been mentioned above. Now let’s reveal the essence of prototype through rules.
We say that prototype behaves like a static field in C. Add a property as a property of prototype. This property will be shared by all instances created by this type, but this sharing is read-only. In any instance, you can only overwrite this property with your own property of the same name, but not change it. In other words, when an object reads a certain attribute, it always checks the attribute table of its own domain first. If there is such an attribute, it will return this attribute. Otherwise, it will read the prototype domain and return the attributes on the prototype domain. In addition, JavaScript allows the prototype field to refer to any type of object. Therefore, if the attribute is not found after reading the prototype field, JavaScript will recursively search the prototype field of the object pointed to by the prototype field until the prototype field of the object is it. itself or until a cycle occurs, we can use the following diagram to describe the relationship between prototype and object instance:
//TODO:
4 The value and limitations of prototype
From From the above analysis, we understand prototype, through which we can use an object as a prototype to safely create a large number of instances. This is the true meaning of prototype and its value. We will see later that using this feature of prototype can be used to simulate the inheritance of objects, but you must know that although prototype is used to simulate inheritance, although it is also an important value of it, it is definitely not its core. In other words, JavaScript Therefore, supporting prototype is definitely not just used to implement its object inheritance. Even without prototype inheritance, JavaScript's prototype mechanism is still very useful.
Since prototype only uses objects as prototypes to build copies of types, it also has great limitations. First of all, it does not behave as a value copy in the prototype field of the type, but as a reference copy, which brings "side effects". Changing the property value of a reference type property on a prototype (another rather awkward explanation :P) will completely affect every instance created of this type. Sometimes this is exactly what we need (such as changing the default value of all objects of a certain class), but sometimes this is also what we don't want (such as when class inheritance). Here is an example:
<script> function ClassA() { this.a=[]; } function ClassB() { this.b=function(){}; } ClassB.prototype=new ClassA(); var objB1=new ClassB(); var objB2=new ClassB(); objB1.a.push(1,2,3); alert(objB2.a); //所有b的实例中的a成员全都变了!!这并不是这个例子所希望看到的。 </script>