What is the prototype
Function type has a property prototype, which is translated directly into the prototype. This property is a pointer, pointing to an object, which contains some properties and methods, which will be shared by all instances (objects) generated by the current function.
Based on what was said before, you can get the following code:
function Person(){ ...}Person.prototype = { country : 'china', sayName : function(){ ... }}First, an instance person of Function type is created, and then the person's method prototype is an object, and the declaration points to an object. The properties and methods in this object will be shared by the instance generated by the current person function. That is to say:
person1 = new Person(); person2 = new Person();
person1 and person2 are both generated again through Person Function type instances. Both have the common property country and method sayName, because they all have a pointer (__proto__), pointing directly to the object pointed to by Person.prototype. However, please note that the __proto__ pointer is not standard. It is only defined by browsers such as Chrome and Firefox. In fact, this property will not be used, but is only used as an understanding of prototype:
Regarding the usage of prototypes and other methods, we will talk about it more specifically later.
Create an object pattern
Next, let’s take a look at the methods and common patterns for creating objects, as well as their advantages and disadvantages.
1. Factory model
Just like a factory, the process of creating concrete objects is abstracted, and functions are used to encapsulate the details of creating objects with specific interfaces. By using functions instead of partial repetition work, the code is as follows:
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o;}var person1 = createPerson("jiangshui","22","engineer");This creates a person, and the factory pattern solves the problem of repeated creation of multiple similar objects, but does not solve the object recognition problem. It is just that an object is simply created, and no matter whether this object is created from a human template or an animal template, it is impossible to distinguish the type of this object.
2. Constructor mode
Create a custom constructor to define properties and methods of custom object types.
function Person(name, age, job){ this.name = name; this.age = age; this.job = jpb; this.sayName = function(){ alert(this.name); };};var person1 = new Person(...);3. The difference between constructor mode and factory mode:
Person is an object of Function type. After new, an object will continue to be generated. However, since this newly generated object is passed in the function and assigned to this pointer, the content passed in becomes the property or method of the newly generated object.
The default habit of constructors is capitalized in the first letter. The above code execution goes through the following steps:
In the instances generated in this way, they all contain a constructor attribute by default pointing to the constructor function, for example:
alert(person1.constructor == Person);
Therefore, using the constructor pattern, there is a type distinction, and its instance can be identified as a specific type.
In addition, the constructor is an ordinary function. Because you want to feedback to get a new object, you use new to call it. If not, executing it directly is like a normal function. For example, if you execute Person.sayName() above, window.name will pop up because the function is executed under window, so this point to window.
The constructor mode is also flawed. The methods in the constructor mode are recreated on each instance, so functions of the same name on different instances are not equal. For example:
person1.sayName == person2.sayName; //false
That is to say, each object instance, attributes and methods generated by the constructor are unique and are copied. Attributes are unique, because this is exactly the difference between objects, but many methods have the same functions and code. If you copy them repeatedly many times, you will obviously waste resources.
So we can put the function outside and then point to the function with a pointer in the constructor. In the generated instance, the method stores a pointer to a certain function, which means a shared function:
function Person(name, age){ this.name = name; this.age = age; this.sayName = sayName;}function saysName(){ alert(this.name);}However, in this way, this function becomes a global function, and it is not highly correlated with the Person constructor and has no encapsulation.
Next, please come to the prototype mode.
Prototype mode
A part of the basics about prototypes has been introduced earlier. Simply put, each function has a prototype attribute, pointing to an object (prototype object), and some properties or methods can be placed in this object. Then the instance generated by this function will have an irregular attribute (__proto__) pointing to the prototype.
From this point of view, you should be able to understand that the properties and methods generated by prototype are shared by all instances.
This just solves the problem of sharing functions in the above constructor mode and in the examples. For example, the following code:
function Person(){ ....}Person.prototype.name = "jiangshui";Person.prototype.sayName = function(){ alert(this.name);};var person1 = new Person(); person1.sayName(); //jiangshuior
Person.prototype = { constructor : Person, name : "jiangshui", sayName : function(){ alert(this.name); }};The second method covers the entire prototype object, so you need to manually specify the constructor property, pointing to the constructor function, otherwise it will point to the Object.
Let's sort out their relationship:
Use isPrototypeOf() to determine the relationship between objects. For example:
Person.prototype.isPrototypeOf(person1);
When the code reads a certain property of an object, a search will be performed. Start with the current object, and if not, search for the prototype object pointed to by the pointer, without searching for the constructor. The object instance can be accessed but cannot override the value of the prototype object. If an attribute with the same name as the prototype object is set in the instance, the search process ends in the instance without accessing the prototype object, so the purpose of overwriting is achieved. Therefore, even if this property is set to null, it means that the property already exists in the instance, and the property will not be cancelled, so that the corresponding property of the prototype can be accessed.
Therefore, you need to use the delete operator to completely delete the instance attributes so that the prototype can be revisited.
The prototype is dynamic, and any modifications made to the prototype object can be immediately reflected from the instance. The reason is the loose link relationship between the instance and the prototype. Every time the instance's property method is called, a query will be conducted. If the prototype changes, the query result will also change.
After understanding the prototype, we can also add new methods or attributes to the native object. Native reference types such as Object, Array, String, etc. are similar to the constructors above. We can use prototype to expand their methods. For example:
String.prototype.startsWith = function(text){ return this.indexOf(text) == 0;};var msg = "Hello World";msg.startsWith("Hello");This code adds a startsWith method to the native reference type String, which is to pass in a parameter to see if the string to be tested starts with a parameter. Due to the dynamic nature of the prototype, all variables of string type get this method by executing it.
However, this method is not recommended. If you use too much and too much code, it will lead to maintenance difficulties, confusion in code, etc. Generally speaking, a native reference type will be inherited first, and then created on a newly customized type. Regarding inheritance, we will summarize it later.
The prototype pattern is not omnipotent either. All attributes and methods in the prototype are shared by all instances, so it is very suitable for functions and other functions, but for attributes containing reference types, some conflicts will arise. For example:
function Person(){}Person.prototype = { constructor : Person, friends : ["greg","jack"]};var person1 = new Person();var person2 = new Person();person1.friends.push("tom");console.log(person2.friends);You will see in console that there is an extra tom for person2 friends, which is not what I want, but when defining his friend to person1, it does affect the instance person2.
So we need to use it in combination with prototype pattern and constructor pattern.
Use constructor mode and prototype mode in combination
This is the most commonly used pattern. The constructor is used to define instance properties and customize by passing parameters; the prototype is used to define methods or attributes that require sharing among all instances. In this way, customization is achieved, sharing is ensured, and problems are avoided.
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["greg","jack"];}Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); }};var jiangshui = new Person("jiangshui","22","engineer");Practical application examples
OK, here you may understand what the prototype is and how to create objects, but what are the uses of these? Indeed, my previous work was just to write some code using jQuery, and I couldn't use encapsulation and then generate objects to implement functions, etc. So what are the uses of these?
This development method is mainly used for modular and assembly development. For example, the pop-up function you often use, of course you can paste and copy the pop-up code every time, and then modify it and use it in the project. A better choice is to abstractly encapsulate your pop-up function code into such a component, so that when you need to use pop-ups, you only need to pass parameters to generate a pop-up instance, and you can call it.
Prototype objects and prototype chains
In Javascript, everything is an object, but there are also differences in objects. It can be roughly divided into two categories, namely: ordinary objects (Object) and function objects (Function).
Generally speaking, the objects generated through the new Function are function objects, and other objects are ordinary objects.
Give an example:
function f1(){ //todo}var f2 = function(){ //todo};var f3 = new Function('x','console.log(x)'); var o1 = {};var o2 = new Object();var o3 = new f1(); console.log( typeof f1,//function typeof f2,//function typeof f3,//function typeof o1,//object typeof o2,//object typeof o3 //object);>> function function object object object objectf1 belongs to the declaration of a function. The most common way to define a function is that f2 is actually an anonymous function. Assign this anonymous function to f2, which belongs to a function expression. f3 is not common, but it is also a function object.
Function is an object that comes with JS. When f1 and f2 are created, JS will automatically build these objects through new Function(). Therefore, these three objects are created through new Function().
There are two ways to create objects in Javascript: object literals and new expressions. The creation of o1 and o2 just corresponds to these two ways. Let’s focus on o3. If you use the ideas of Java and C# to understand, o3 is an instance object of f1, and o3 and f1 are the same type. At least I thought so before, but it is not...
So how do you understand it? It's very simple. See if o3 is generated through new Function. Obviously not. Since it is not a function object, it is an ordinary object.
After a simple understanding of function objects and ordinary objects, let’s learn about prototypes and prototype chains in Javascript:
In JS, whenever a function object f1 is created, some properties are built into the object, including prototype and __proto__, prototype is the prototype object, which records some properties and methods of f1.
It should be noted that prototype is invisible to f1, that is, f1 will not look for properties and methods in prototype.
function f(){}f.prototype.foo = "abc";console.log(f.foo); //undefinedSo, what's the use of prototype? In fact, the main function of prototype is inheritance. In layman's terms, the properties and methods defined in prototype are all left to their "descendants", so subclasses can fully access the properties and methods in prototype.
To know how f1 leaves prototypes for "descendants", we need to understand the prototype chain in JS. At this time, the __proto__ in JS has entered the market. This guy is very strange and hidden, so that you often don't see it, but it exists in both ordinary objects and function objects. Its function is to save the prototype object of the parent class. When JS creates an object through a new expression, it usually assigns the prototype of the parent class to the __proto__ attribute of the new object, which forms a generation of inheritance...
function f(){}f.prototype.foo = "abc";var obj = new f();console.log(obj.foo); //abcNow we know that __proto__ in obj saves the prototype of f, so what is saved in __proto__ in f's prototype? See the following picture:
As shown in the figure, the Object.prototype stored in the __proto__ of f.prototype, and there is also __proto__ in the Object.prototype object., and from the output result, Object.prototype.__proto__ is null, indicating the end of the obj object prototype chain. As shown in the figure below:
After the obj object has such a prototype chain, when obj.foo is executed, obj will first find whether it has the attribute, but will not find its own prototype. When foo cannot be found, obj will search in turn along the prototype chain...
In the above example, we define the foo attribute on the prototype of f, and then obj will find this attribute on the prototype chain and execute it.