The concept of prototype in JavaScript appropriately reflects the connotation of this word. We cannot understand it as a pre-declared concept like C's prototype.
All function type objects in JavaScript have a prototype attribute. The prototype attribute itself is an object type object, so we can also add arbitrary properties and methods to this prototype object. Since prototype is the "prototype" of an object, then the objects constructed by this function should have the characteristics of this "prototype". In fact, all properties and methods defined on the prototype of the constructor can be directly accessed and called through the object it constructs. It can also be said that prototype provides a mechanism for a group of similar objects to share properties and methods.
Let’s take a look at the following code first:
function Person(name)
{
this.name = name; //Set object attributes, each object has its own attribute data
};
Person.prototype.SayHello = function() //Add the SayHello method to the prototype of the Person function.
{
alert("Hello, I'm " this.name);
}
var BillGates = new Person("Bill Gates"); //Create BillGates object
var SteveJobs = new Person("Steve Jobs"); //Create SteveJobs object
BillGates.SayHello(); //Directly call the SayHello method through the BillGates object
SteveJobs.SayHello(); //Directly call through the SteveJobs object Go to the SayHello method
alert(BillGates.SayHello == SteveJobs.SayHello); //Because the two objects share the prototype of SayHello, it displays: true
The result of running the program shows that the constructor is defined on the prototype Methods can indeed be called directly through objects, and the code is shared. Obviously, the writing method of setting the method to prototype is much more elegant. Although the calling form has not changed, it logically reflects the relationship between the method and the class. Compared with the previous writing method, it is easier to understand and organize the code.
So, what about constructors for multi-level types?
Let’s look at the following code again:
1 function Person(name) //base class constructor
2 {
3 this.name = name;
4 };
5
6 Person.prototype.SayHello = function() //Add a method to the prototype of the base class constructor
7 {
8 alert("Hello, I'm " this.name);
9 };
10
11 function Employee(name, salary) //Subclass constructor
12 {
13 Person.call(this, name); //Call base class constructor
14 this.salary = salary;
15 };
16
17 Employee.prototype = new Person(); //Create a base class object as the prototype of the subclass prototype. This is very interesting.
18
19 Employee.prototype.ShowMeTheMoney = function() //Add a method to the prototype of the subclass's constructor
20 {
21 alert(this.name " $" this.salary) ;
22 };
23
24 var BillGates = new Person("Bill Gates"); //Create a BillGates object of base class Person
25 var SteveJobs = new Employee("Steve Jobs" , 1234); //Create the SteveJobs object of the subclass Employee
26
27 BillGates.SayHello(); //Directly call the prototype method through the object
28 SteveJobs.SayHello(); //Pass The subclass object directly calls the method of the base class prototype, pay attention!
29 SteveJobs.ShowMeTheMoney(); // Directly call the method of the subclass prototype through the subclass object
30
31 alert(BillGates.SayHello == SteveJobs.SayHello); //Display: true, indicating The prototype method is shared
Line 17 of this code constructs a base class object and sets it as the prototype of the subclass constructor, which is very interesting. The purpose of this is for line 28. The method of the base class prototype can also be directly called through the subclass object. Why is this possible?
It turns out that in JavaScript, prototypes not only allow objects to share their wealth, but prototypes also have the nature of tracing their roots and ancestors, so that the legacy of our ancestors can be passed down from generation to generation. When reading attributes or calling methods from an object, if the object itself does not have such attributes or methods, it will go to the prototype object associated with itself to find it; if the prototype does not have it, it will go to the predecessor prototype associated with the prototype itself. Search until found or until the tracing process is completed.
Inside JavaScript, the object’s attribute and method tracing mechanism is implemented through the so-called prototype chain. When an object is constructed using the new operator, the prototype object of the constructor is also assigned to the newly created object and becomes the built-in prototype object of the object. The built-in prototype object of an object should be invisible to the outside world. Although some browsers (such as Firefox) can allow us to access this built-in prototype object, this is not recommended. The built-in prototype object itself is also an object and has its own associated prototype object, thus forming a so-called prototype chain.
At the end of the prototype chain is the prototype object pointed to by the prototype attribute of the Object constructor. This prototype object is the oldest ancestor of all objects. This ancestor implements methods such as toString that all objects should inherently have. The prototypes of other built-in constructors, such as Function, Boolean, String, Date and RegExp, are all inherited from this ancestor, but they each define their own properties and methods, so that their descendants show the characteristics of their respective clans. Those characteristics.
Isn’t this just “inheritance”? Yes, this is "inheritance", a "prototypal inheritance" unique to JavaScript.
"Prototypal inheritance" is both kind and harsh. The prototype object selflessly contributes its properties and methods to the children, and does not force the children to comply, allowing some naughty children to act independently according to their own interests and hobbies. In this regard, the archetypal subject is a loving mother. However, although any child can go his own way, he cannot touch the existing property of the prototype object, because that may affect the interests of other children. From this point of view, the prototype object is like a strict father. Let’s take a look at the following code to understand what this means:
function Person(name)
{
this.name = name;
};
Person.prototype.company = " Microsoft"; //Prototype properties
Person.prototype.SayHello = function() //Prototype methods
{
alert("Hello, I'm " this.name " of " this.company );
};
var BillGates = new Person("Bill Gates");
BillGates.SayHello(); //Due to inheriting the prototype, the output is regular: Hello, I'm Bill Gates
var SteveJobs = new Person("Steve Jobs");
SteveJobs.company = "Apple"; //Set your own company property, covering up the prototype's company property
SteveJobs.SayHello = function () //Implemented my own SayHello method, covering up the prototype's SayHello method
{
alert("Hi, " this.name " like " this.company ", ha ha ha ");
};
SteveJobs.SayHello(); //They are all properties and methods covered by themselves, output: Hi, Steve Jobs like Apple, ha ha ha
BillGates.SayHello(); //The overwriting of SteveJobs has no effect For prototype objects, BillGates still outputs the same as before. The
object can mask the properties and methods of the prototype object. A constructor prototype object can also mask the existing properties and methods of the upper-layer constructor prototype object. This masking actually just creates new properties and methods on the object itself, but these properties and methods have the same names as those of the prototype object. JavaScript uses this simple masking mechanism to achieve the "polymorphism" of objects, which coincides with the concepts of virtual functions and override in static object languages.
However, what is more magical than static object language is that we can dynamically add new properties and methods to the prototype object at any time, thus dynamically extending the functional features of the base class. This is difficult to imagine in a static object language. Let’s look at the following code:
function Person(name)
{
this.name = name;
};
Person.prototype.SayHello = function() //Before creating the object Defined method
{
alert("Hello, I'm " this.name);
};
var BillGates = new Person("Bill Gates"); //Create object
BillGates.SayHello();
Person.prototype.Retire = function() //Method to dynamically extend the prototype after creating an object
{
alert("Poor " this.name ", bye bye! ");
};
BillGates.Retire(); //The dynamically extended method can be called immediately by the previously created object
Amitābha, prototypal inheritance can actually play such a magic!
Prototype Extension
You must have a very high level of understanding. You may think this way: If you add some new methods and properties to the prototype of JavaScript’s built-in functions such as Object and Function, can you extend JavaScript? What about the function?
So, congratulations, you got it!
With the rapid development of AJAX technology today, the JavaScript runtime libraries of many successful AJAX projects have greatly expanded the prototype functions of built-in functions. For example, Microsoft's ASP.NET AJAX adds a large number of new features to these built-in functions and their prototypes, thus enhancing the functionality of JavaScript.
Let’s look at a piece of code excerpted from MicrosoftAjax.debug.js:
String.prototype.trim = function String$trim() {
if (arguments.length !== 0) throw Error.parameterCount ();
return this.replace(/^s |s $/g, '');
}
This code extends a trim method to the prototype of the built-in String function, so all String class objects now have a trim method. With this extension, in the future, if you want to remove the whitespace between two sections of a string, you no longer need to process it separately, because any string has this extension function, and you only need to call it, which is really convenient.
Of course, few people add methods to the prototype of Object, because that will affect all objects, unless this method is indeed required by all objects in your architecture.
Two years ago, in the early days of designing the AJAX class library, Microsoft used a technology called "closure" to simulate "classes".The rough model is as follows:
function Person(firstName, lastName, age)
{
//Private variables:
var _firstName = firstName;
var _lastName = lastName;
// Public variables:
this.age = age;
//Method:
this.getName = function()
{
return(firstName " " lastName);
};
this.SayHello = function()
{
alert("Hello, I'm " firstName " " lastName);
};
};
var BillGates = new Person( "Bill", "Gates", 53);
var SteveJobs = new Person("Steve", "Jobs", 53);
BillGates.SayHello();
SteveJobs.SayHello();
alert(BillGates.getName() " " BillGates.age);
alert(BillGates.firstName); //Private variables cannot be accessed here
Obviously, the class description of this model is particularly like the C# language The description form defines private members, public properties and available methods in a constructor, which is very elegant. In particular, the "closure" mechanism can simulate the protection mechanism of private members, which is very beautiful.
The so-called "closure" is to define another function in the constructor body as the method function of the target object, and the method function of this object in turn refers to the temporary variable in the outer outer function body. This allows the temporary variable values used by the original constructor body to be indirectly maintained as long as the target object can always maintain its methods during its lifetime. Although the initial constructor call has ended and the name of the temporary variable has disappeared, the value of the variable can always be referenced in the method of the target object, and the value can only be accessed through this method. Even if the same constructor is called again, only new objects and methods will be generated. The new temporary variables only correspond to new values and are independent from the last call. Very clever indeed!
But as we said before, setting a method for each object is a big waste. In addition, "closure", a mechanism that indirectly maintains variable values, often creates problems for JavaScript's garbage collector. Especially when encountering complex circular references between objects, the judgment logic of garbage collection is very complicated. Coincidentally, early versions of IE did have memory leaks in JavaScript garbage collection. Coupled with the poor performance of the "closure" model in performance testing, Microsoft finally abandoned the "closure" model and switched to the "prototype" model. As the saying goes, "Everything you gain must come with a loss."
The prototype model requires a constructor to define the members of the object, and methods are attached to the prototype of the constructor. The rough writing is as follows:
//Define the constructor
function Person(name)
{
this.name = name; //Define members in the constructor
};
/ /The method is defined on the prototype of the constructor
Person.prototype.SayHello = function()
{
alert("Hello, I'm " this.name);
};
//Subclass constructor
function Employee(name, salary)
{
Person.call(this, name); //Call upper-level constructor
this.salary = salary; //Extended Members
};
//The subclass constructor first needs to use the upper-level constructor to create the prototype object to realize the concept of inheritance
Employee.prototype = new Person() //Only its prototype method is needed , the members of this object have no meaning!
//Subclass methods are also defined above the constructor
Employee.prototype.ShowMeTheMoney = function()
{
alert(this.name " $" this.salary);
};
var BillGates = new Person("Bill Gates");
BillGates.SayHello();
var SteveJobs = new Employee("Steve Jobs", 1234);
SteveJobs.SayHello( );
SteveJobs.ShowMeTheMoney();
Although the prototype class model cannot simulate real private variables, it also has to be divided into two parts to define the class, which is not very "elegant".However, methods are shared between objects, do not encounter garbage collection problems, and perform better than the "closure" model. As the saying goes, "Everything you lose must have a gain."
In the prototype model, in order to implement class inheritance, the prototype of the subclass constructor must first be set to an object instance of the parent class. The purpose of creating this parent class object instance is to form a prototype chain to share the upper prototype method. But when this instance object is created, the upper-level constructor will also set object members to it. These object members are meaningless for inheritance. Although we did not pass parameters to the constructor, we did create several useless members, although their values are undefined, which is also a waste.
Alas! Nothing is perfect in the world!
The True Truth of Archetype
Just when we were overwhelmed with emotion, a red light flashed in the sky, and Guanyin Bodhisattva appeared in the auspicious clouds. I saw her holding a jade purification bottle, flicking the green willow branches, and sprinkling a few drops of nectar, which immediately gave JavaScript a new aura.
The nectar sprinkled by Guanyin condensed into blocks in the JavaScript world and became something called "grammatical nectar". This syntactic nectar can make the code we write look more like an object language.
If you want to know what this "grammatical nectar" is, please listen carefully.
Before understanding these grammatical nectar, we need to review the process of constructing objects in JavaScript.
We already know that the process of creating an object in the form of var anObject = new aFunction() can actually be divided into three steps: the first step is to create a new object; the second step is to set the built-in prototype object of the object to the constructor The prototype object referenced by the function prototype; the third step is to call the constructor using the object as this parameter to complete initialization work such as member setting. After the object is created, any access and operation on the object are only related to the object itself and the string of objects on the prototype chain, and have nothing to do with the constructor. In other words, the constructor only plays the role of introducing the prototype object and initializing the object when creating an object.
So, can we define an object ourselves as a prototype, describe the class on this prototype, and then set this prototype to the newly created object and treat it as the class of the object? Can we use a method in this prototype as a constructor to initialize the newly created object? For example, we define such a prototype object:
var Person = //Define an object as a prototype class
{
Create: function(name, age) //This is used as a constructor function
{
this.name = name;
this.age = age;
},
SayHello: function() //Define method
{
alert("Hello, I'm " this.name);
},
HowOld: function() //Define method
{
alert(this.name " is " this.age " years old.");
}
};
The way this JSON is written looks like a C# class! There are both constructors and various methods. If you can create an object in some form and set the object's built-in prototype to the "class" object above, wouldn't it be equivalent to creating an object of that class?
But unfortunately, we can hardly access the built-in prototype properties of the object! Although some browsers can access the object's built-in prototype, doing so only restricts the browser to which the user must use. This is also almost impossible.
So, can we use a function object as a medium, use the prototype attribute of the function object to transfer the prototype, and use the new operator to pass it to the newly created object?
In fact, code like this can achieve this goal:
function anyfunc(){}; //Define a function shell
anyfunc.prototype = Person; //Put the prototype object into the transfer station prototype
var BillGates = new anyfunc(); //The built-in prototype of the new object will be the prototype object we expect
However, this anyfunc function is just a shell, and it becomes redundant after using this shell It's something, and it's no different from using the constructor directly to create an object, which is a bit unpleasant.
However, if we write these codes as a general function, and the function body becomes a function within the function, wouldn’t this inner function automatically die after the outer function exits the scope? Moreover, we can use the prototype object as a parameter of the general function and let the general function return the created object.What we need is the following form:
function New(aClass, aParams) //Generally created function
{
function new_() //Define a temporary transit function shell
{
aClass .Create.apply(this, aParams); //Call the constructor defined in the prototype and transfer the construction logic and construction parameters
};
new_.prototype = aClass; //Prepare to transfer the prototype object
return new new_(); //Return the final created object
};
var Person = //The defined class
{
Create: function(name, age)
{
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " this.name) ;
},
HowOld: function()
{
alert(this.name " is " this.age " years old.");
}
};
var BillGates = New(Person, ["Bill Gates", 53]); //Call a general function to create an object and pass the construction parameters in the form of an array
BillGates.SayHello();
BillGates.HowOld() ;
alert(BillGates.constructor == Object); //Output: true
The general function New() here is a "grammatical nectar"! This syntactic nectar not only transfers the prototype object, but also transfers the constructor logic and construction parameters.
What’s interesting is that every time the object is created and exits the New function scope, the temporary new_function object will be automatically released. Since the prototype attribute of new_ is set to the new prototype object, the reference chain between the original prototype object and new_ has been untied, and the temporary function and its original prototype object will be correctly recycled. The last sentence of the above code proves that the constructor property of the newly created object returns the Object function. In fact, the newly created object itself and its prototype do not have a constructor attribute, so what is returned is only the constructor of the top-level prototype object, that is, Object.
With the syntax nectar of New, the definition of a class is very similar to the static object language form of C#. How quiet and elegant such code looks!
Of course, this code only demonstrates the concept of "grammatical nectar". We also need more syntax to write class hierarchies and their inheritance relationships in concise and elegant code. Okay, let’s look at a richer example:
//Syntax nectar:
var object = //Define the lowercase object basic class, used to implement the most basic methods, etc.
{
isA: function(aType) //A basic method for judging the relationship between classes and objects and classes
{
var self = this;
while(self)
{
if (self == aType)
return true;
self = self.Type;
};
return false;
}
};
function Class( aBaseClass, aClassDefine) //Function to create a class, used to declare the class and inheritance relationship
{
function class_() //Create a temporary function shell of the class
{
this.Type = aBaseClass; //We agree on a Type attribute for each class and refer to its inherited class
for(var member in aClassDefine)
this[member] = aClassDefine[member]; //Copy all definitions of the class to the current creation Class
};
class_.prototype = aBaseClass;
return new class_();
};
function New(aClass, aParams) //Function to create an object, used for any Class object creation
{
function new_() //Create a temporary function shell of the object
{
this.Type = aClass; //We also agree on a Type attribute for each object. According to This can access the class to which the object belongs
if (aClass.Create)
aClass.Create.apply(this, aParams); //We agree that the constructor of all classes is called Create, which is similar to DELPHI
};
new_.prototype = aClass;
return new new_();
};
//The application effect of syntax nectar:
var Person = Class(object, // Derived from the object base class
{
Create: function(name, age)
{
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " this.name ", " this.age " years old.");
}
});
var Employee = Class(Person, // Derived from the Person class, is it very similar to the general object language?
{
Create: function(name, age, salary)
{
Person.Create.call(this, name, age); //Call the constructor of the base class
this. salary = salary;
},
ShowMeTheMoney: function()
{
alert(this.name " $" this.salary);
}
});
var BillGates = New(Person, ["Bill Gates", 53]);
var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]);
BillGates.SayHello();
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
var LittleBill = New(BillGates.Type, ["Little Bill", 6]); //Create LittleBill based on the type of BillGate
LittleBill .SayHello();
alert(BillGates.isA(Person)); //true
alert(BillGates.isA(Employee)); //false
alert(SteveJobs.isA(Person)); //true
alert(Person.isA(Employee)); //false
alert(Employee.isA(Person)); //true
You don’t need too much "grammatical nectar", just a little bit , can improve the readability and fluency of the entire code, making the code more elegant. With these nectar of syntax, JavaScript becomes much like a general object language, and it feels much better to write code!
The good news is that JavaScript programs nourished by these nectars will be more efficient.Because there are no useless object-level members in its prototype object, and there is no constructor property body, there is less involvement with the constructor, but the sharing of methods is still maintained. This saves JavaScript a lot of time when tracing the prototype chain and searching for properties and methods.
Let’s call this form the “manna model”! In fact, the prototype usage of this "manna model" is in line with the original meaning of the prototype concept and is the true meaning of JavaScript prototype!
I think when the engineers who designed the AJAX architecture at Microsoft saw this nectar model, they must have regretted not moving the AJAX department from the United States to the Guanyin Temple in China earlier, and missed the enlightenment of Guanyin Bodhisattva. Of course, we can only play with Bill Gates as an object in the code example. It will definitely not be easy for him to give up God and convert to my Buddha. The opportunity has not yet come! If one day you see this kind of nectar model in Microsoft's new AJAX class library, that is true fate!
The joy of programming
Today with the rapid development of the software industry, various programming languages are emerging in endlessly. The birth of new languages and the evolution of old languages seem to have dazzled us. In order to adapt to the trend of object-oriented programming, the JavaScript language is also developing in a completely object-oriented direction. The new JavaScript standard has semantically extended many new object-oriented elements. On the contrary, many static object languages are also developing in the direction of JavaScript's simplicity and elegance. For example, new versions of the C# language incorporate concise representations like JSON, as well as some other forms of JavaScript features.
We should see that with the development and popularization of RIA (Strong Internet Application), AJAX technology will gradually fade out of the world, and JavaScript will eventually disappear or evolve into other forms of language. But no matter how programming languages develop and evolve, the programming world will always maintain infinite vitality in the inextricable entanglement of "data" and "code". As long as we can see through this, we can easily learn and understand various new things in the software world. Whether it is the familiar procedural programming, the developing functional programming, or the massively parallel programming of future quantum entanglement states, we have enough magic power to solve all complex problems.