preface
JavaScript is a language that is all objects except its basic types. It can be said that the core of JavaScript is objects. Therefore, it is very important to understand JavaScript objects and their various features. This article introduced my understanding of ES5 objects, stereotypes, stereotype chains, and inheritance
Note (this article is extremely long) that this article is just my personal understanding of JavaScript objects, not a tutorial. This article was written after I had just learned about JS objects. There must be some mistakes in this article, please kindly point them out by commenting below
What are JavaScript objects
var person = { // Person is an object. Objects have various attributes, and each attribute has its own value
// Key-value pair form
name: "Mofan".// Can contain strings
age: 20./ / digital
parents: [ / / array
"Daddy"."Mami",
]
sayName: function(){ / / function
console.log(this.name);
},
features: { / / object
height: "178cm".weight: "60kg",}}Copy the code
Everything except basic types in js is an object:
-
Function sayName(){} — sayName is the function object
-
Var arr = new Array() — arr is an Array object
Why is JavaScript designed this way? I think first of all, it’s unifying the data structure, making JavaScript a scripting language with a very liberal programming style: var whatever variable is defined; Second, JavaScript objects have properties and methods, and arrays of functions are objects, making calling references very flexible and convenient. Again, to build a prototype chain?
Several ways to create objects
-
The Object() pattern uses Object literals: var obj={… } as above or use the native constructor Object() :
var person = new Object(a); person.name ="Mofan";
person.sayName = function(){
console.log(this.name);
};
console.log(person.name);//Mofan
obj.sayName();//Mofan
Copy the code
-
Mock classes using custom constructor patterns using function scopes (constructor patterns) :
function Person(name,age){
this.name = name;
this.age = age;
this.print = function(){
console.log(this.name + this.age)
};
}
var person = new Person("Mofan".19);
console.log(person.name+person.age);//Mofan19
person.print();//Mofan19Copy the code
-
Prototype mode:
function Person(){}
// You can write it like this
/*Person.prototype.name = "Mofan"; Person.prototype.age = 19; Person.prototype.print = function(){ console.log(this.name+this.age); } * /
// The following is recommended, but the two methods should not be mixed! Because the following way is actually rewritten
// The Person prototype object. If the two are mixed, the latter assignment overrides the previous one
Person.prototype = {
name:"Mofan".age:19.print:function(){
console.log(this.name+this.age); }}var person = new Person();
console.log(person.name+person.age);//Mofan19
person.print();//Mofan19Copy the code
-
Combined constructor pattern and stereotype pattern:
function Person(name,age){
// This is where the attributes are initialized
this.name = name;
this.age = age; . } Person.prototype = {// Public methods are defined here
print:function(){
console.log(this.name+this.age); },... }var person = new Person("Mofan".19);
console.log(person.name+person.age);//Mofan19
person.print();//Mofan19Copy the code
-
Dynamic prototyping mode:
function Person(name,age){
// Initialize the property
this.name = name;
this.age = age;
// All public methods are defined when the first object is created (the first time it is called) and will not be called again
if(typeof this.print ! ="function"){
Person.prototype.print =function(){
console.log(this.name+this.age);
};
Person.prototype.introduction=function(){
console.log("Hi! I'm "+this.name+",I'm "+this.age);
};
// If you add methods to the prototype using object literals, the object created for the first time will not have those methods
};
}
var person = new Person("Mofan".19);
person.print();//Mofan19
person.introduction();//Hi! I'm Mofan,I'm 19Copy the code
Other patterns use fewer scenarios
Application scenarios for these patterns
How can there be so many creation modes? In fact, the JS language is so flexible, so the predecessors concluded that there are several ways to create for different scenarios, each of which has its own advantages and disadvantages.
-
The first way, using literals or using the constructor Object(), is often used to create ordinary objects to store data and so on. Their prototypes are all Object and have no relation to each other. In fact, the following is the same:
var o1 = {};// The literal representation
var o2 = new Object;
var o3 = new Object(a);var o4 = new Object(null);
var o5 = new Object(undefined);
var o6 = Object.create(Object.prototype);Var o = {}; // Create an Object based on the Object. Prototype templateCopy the code
-
The second way is to use function scope to mimic classes, so that you can refer to them when creating objects. You can create objects with different properties and implement object customization. The print method is also defined in the constructor, so if we were to use it as a public method, we would have this method for every new object, which would be a waste of memory. The constructor schema can be modified as follows:
// Constructor method 2
function print(){ // Define a global Function object that takes out the public methods
console.log(this.name + this.age);
}
function Person(name,age){
this.name = name;
this.age = age;
this.print = print.bind(this);// Each Person object shares the same print method version (the method has its own scope, so don't worry about variables being shared)
}
var person = new Person("Mofan".19);
console.log(person.name+person.age);//Mofan19
person.print();//Mofan19
Copy the code
This, however, looks messy and doesn’t encapsulate classes. Use a prototype
-
The third way, pure prototype mode, is to add both properties and methods to the prototype. This has the advantage of saving memory, but the scope of application is less. More properties inside the object need to be customized, and once the prototype is changed, all the prototype instances will be changed. Therefore, we can combine the constructor approach to achieve object customization, which leads to a fourth approach — the combination of constructor and stereotype patterns, which can be customized in the constructor, and common in the stereotype, which also conforms to the characteristics of the constructor and stereotype. “This is the most widely used and recognized method of creating custom types in ES5” – JavaScript Advanced Programming, 3rd edition
-
The fifth approach, the dynamic prototype pattern, arises because some object-oriented developers, accustomed to class constructors, are confused and uncomfortable with the separate constructors and stereotypes. As a result, the dynamic prototype pattern appears, with the definition stereotype also written into the constructor. In dynamic prototype model about “if the object literal to prototype method, the addition of objects created for the first time will not have these methods” that is because before the if statement is executed, the first object has been created, and then execute the if statement inside, if use object literal to prototype assignment, This will result in the stereotype being rewritten after the instance is created, and the first instance created will lose the link to the stereotype, and there will be no methods in the stereotype. However, objects created later can use the methods in the prototype because they are created after the prototype has been modified.
What is the prototype
In JavaScript, a prototype is just an object, and there is no need to treat a prototype differently than any other object, except that it allows you to inherit properties from one object to another. Any object can also be a prototype. The reason why we often talk about the prototype of an object is actually to find the object from which the object inherits. An object is called a prototype relative to a prototype, that is, an object that calls an inherited object a prototype can itself be called a prototype of an object next in the prototype chain.
The [[Prototype]] property inside an object is created from birth and refers to the inherited object, called the Prototype. The prototype property inside the function object is also created naturally (only the function object has the Prototype property) and points to the function’s prototype object (not the function’s prototype!). . Var instance = new Class(); This way, every time a new function (which is used as a constructor) creates an instance, JavaScript assigns a reference to that Prototype to the instance’s Prototype property, so the [[Prototype]] property inside the instance points to the function’s Prototype object, the Prototype property.
A Prototype really refers to the [[Prototype]] property inside an object, not the Prototype property inside a function object. There is no relation between the two! For the [[Prototype]] property inside an object, different browsers have different implementations:
var a = {};
//Firefox 3.6+ and Chrome 5+
Object.getPrototypeOf(a); //[object Object]
//Firefox 3.6+, Chrome 5+ and Safari 4+
a.__proto__; //[object Object]
//all browsers
a.constructor.prototype; //[object Object]Copy the code
The reason a function object has a prototype property inside it, and you can use that property to create a prototype, is that JavaScript assigns a reference to that prototype to the instance’s prototype property every time a new instance of such a function is created (the function is used as a constructor). Methods and so on defined in the stereotype are shared by all instances, and once a property in the stereotype is defined, it is inherited by all instances (as in the example above). The implications of this in terms of performance and maintenance are self-evident. That’s what constructors are for. (JavaScript doesn’t define constructors, let alone distinguish between constructors and normal functions. Here are some examples:
var a = {} // An ordinary object
function fun(){} // An ordinary function
// Normal objects have no prototype attribute
console.log(a.prototype);//undefined
console.log(a.__proto__===Object.prototype);//true
// Only function objects have the prototype attribute
console.log(fun.prototype);//Object
console.log(fun.__proto__===Function.prototype);//true
console.log(fun.prototype.__proto__===Object.prototype);//true
console.log(fun.__proto__.__proto__===Object.prototype);//true
console.log(Function.prototype.__proto__===Object.prototype);//true
console.log(Object.prototype.__proto__);//nullCopy the code
When you performconsole.log(fun.prototype);
The output isAs you can see, every time a function is created, a Prototype property is created that points to the function’s prototype object (not the function’s prototype) and that prototype object automatically gets the constructor property, which is a pointer to the function that the Prototype property belongs to. while__proto__
Properties are common to every object.
Then look at the top:
function Person(){}// the constructor is capitalized
var person1 = new Person();Person1 is an instance of Person
console.log(person1.prototype);//undefined
console.log(person1.__proto__===Person.prototype);//true
console.log(person1.__proto__.__proto__===Object.prototype);//true
console.log(person1.constructor);//function Person(){}
The Person Function is an instance of the Function constructor
console.log(Person.__proto__===Function.prototype);//true
The prototype Object for Person is an instance of the constructor Object
console.log(Person.prototype.__proto__===Object.prototype);//trueCopy the code
Person1 differs from the ordinary object a above in that it is an instance of the constructor Person. As mentioned earlier:
Var instance = new Class(); This way, every time a new function (which is used as a constructor) creates an instance, JavaScript assigns a reference to that Prototype to the instance’s Prototype property, so the [[Prototype]] property inside the instance points to the function’s Prototype object, the Prototype property.
So the [[Prototype]] property inside person1 points to the Prototype Object of Person, and the [[Prototype]] property inside the Prototype Object of Person points to Object. Prototype, which adds an Object to the Prototype chain. By doing this, Person1 has the method in the constructor’s prototype object.
In addition, the above code console.log(person1.constructor); //function Person(){} has no constructor attribute inside person1, which is found in person1.__proto__ by following the prototype chain.
This diagram illustrates the relationship between prototypes, constructors, and instances:
inheritance
JavaScript does not inherit this existing mechanism, but can emulate it with functions, stereotypes, and prototype chains. Here are three ways to inherit:
Class type inheritance
/ / parent class
function SuperClass(){
this.superValue = "super";
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};
/ / subclass
function SubClass(){
this.subValue = "sub";
}
// Class inheritance assigns a parent class instance to a subclass stereotype. The subclass stereotype and subclass instance have access to properties and methods copied from the parent stereotype and from the parent constructor
SubClass.prototype = new SuperClass();
// Add methods for subclasses
SubClass.prototype.getSubValue = function(){
return this.subValue;
}
/ / use
var instance = new SubClass();
console.log(instance.getSuperValue);//super
console.log(instance.getSubValue);//subCopy the code
This method of inheritance has two obvious disadvantages:
-
Cannot pass a parameter to the parent constructor while instantiating a child class
-
If a common attribute in a parent class has a reference type, it will be shared by all instances in the subclass. If an instance of any subclass changes the reference type, it will affect other subclass instances. Constructor inheritance can be used to solve this problem
Constructor inheritance
/ / parent class
function SuperClass(id){
this.superValue = ["big"."large"];// Reference type
this.id = id;
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};
/ / subclass
function SubClass(id){
SuperClass.call(this,id);// Call the parent constructor and pass the argument
this.subValue = "sub";
}
var instance1 = new SubClass(10);// Can pass arguments to the parent class
var instance2 = new SubClass(11);
instance1.superValue.push("super");
console.log(instance1.superValue);//["big", "large", "super"]
console.log(instance1.id);/ / 10
console.log(instance2.superValue); ["big"."large"]
console.log(instance2.id);/ / 11
console.log(instance1.getSuperValue());//errorCopy the code
This approach addresses the drawbacks of class inheritance, but as you can see in the last line of code, there is no parent class prototype involved, so it violates code reuse. So combine them:
Combination of inheritance
function SuperClass(id){
this.superValue = ["big"."large"];// Reference type
this.id = id;
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};
/ / subclass
function SubClass(id,subValue){
SuperClass.call(this,id);// Call the parent constructor and pass the argument
this.subValue = subValue;
}
SubClass.prototype = new SuperClass();
SubClass.prototype.getSubValue = function(){
return this.subValue;
}
var instance1 = new SubClass(10."sub");// Can pass arguments to the parent class
var instance2 = new SubClass(11."sub-sub");
instance1.superValue.push("super");
console.log(instance1.superValue);//["big", "large", "super"]
console.log(instance1.id);/ / 10
console.log(instance2.superValue); ["big"."large"]
console.log(instance2.id);/ / 11
console.log(instance1.getSuperValue()); ["big"."large"."super"]
console.log(instance1.getSubValue());//sub
console.log(instance2.getSuperValue());//["big", "large"]
console.log(instance2.getSubValue());//sub-subCopy the code
Well, it’s pretty good, except that the superclass constructor is called twice, which causes the second call to create the instance to override the stereotype properties, and both the stereotype and the instance have those properties, which obviously doesn’t perform very well. Let’s start with Crockford’s parasitic inheritance:
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function createAnnther(original){
var clone = object(original);
clone.sayName = function(){
console.log(this.name);
}
return clone;
}
var person = {
name:"Mofan".friends: ["xiaoM"."Alice"."Neo"]};var anotherPerson = createAnnther(person);
anotherPerson.sayName();//"Mofan"
}Copy the code
The idea is to make an existing object into a prototype of a new object and then reinforce it in createAnother. As you can see, Person is just a normal object, so this parasitic inheritance is suitable for creating an enhanced version of an existing object, which is really handy when the primary consideration is inheritance from existing objects rather than constructors. The drawback is that createAnother cannot be reused, and I have to write another function if I want to define other methods for another newly created object. If you look closely, the parasitic pattern is actually giving the prototype to a new object, which then strengthens it.
Wait, I’m a little confused about this, so let’s go back to where we started: What’s the purpose of inheritance? What should I inherit from the parent class? I think depending on what we want from the parent, I want all the common attributes of the parent (in the prototype) and the private attributes of the parent (in the constructor) that can be inherited by custom! The disadvantages of the previous models are mainly due to this:
SubClass.prototype = new SuperClass();Copy the code
So why write this sentence? Do you just want to inherit a stereotype from a parent class? If so, why not:
SubClass.prototype = SuperClass.prototype;Copy the code
The SuperClass. Prototype property is a pointer to the SuperClass prototype. If you assign this pointer to the prototype property of a subclass, then subclass Prototype will also point to the SuperClass prototype. Any change to subclass. prototype is a change to the superclass prototype, which is obviously not acceptable.
Parasitic combinatorial inheritance
If the object() function takes the parent as an argument, it returns an object that inherits from the parent, without calling the parent constructor or affecting the parent, which is perfect.
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function inheritPrototype(subType,superType){
var proto = object(superType.prototype);
proto.constructor = subType;// modify the construcor attributes
subType.prototype = proto;
}
function SuperClass(id){
this.superValue = ["big"."large"];// Reference type
this.id = id;
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};
/ / subclass
function SubClass(id,subValue){
SuperClass.call(this,id);// Call the parent constructor and pass the argument
this.subValue = subValue;
}
inheritPrototype(SubClass,SuperClass);// Inherits the parent stereotype
SubClass.prototype.getSubValue = function(){
return this.subValue;
}
var instance1 = new SubClass(10."sub");// Can pass arguments to the parent class
var instance2 = new SubClass(11."sub-sub");
instance1.superValue.push("super");
console.log(instance1.superValue);//["big", "large", "super"]
console.log(instance1.id);/ / 10
console.log(instance2.superValue);//["big", "large"]
console.log(instance2.id);/ / 11
console.log(instance1.getSuperValue());//["big", "large", "super"]
console.log(instance1.getSubValue());//sub
console.log(instance2.getSuperValue());//["big", "large"]
console.log(instance2.getSubValue());//sub-subCopy the code
The parent class constructor is called only once, and the prototype chain remains the same.
console.log(SubClass.prototype.__proto__===SuperClass.prototype);//ture
console.log(SubClass.prototype.hasOwnProperty("getSuperValue"));//falseCopy the code
Therefore, this is the ideal inheritance for reference types.
conclusion
The ideal way to create objects for inheritance is to combine the constructor pattern with the stereotype pattern (or dynamic stereotype pattern), with definable private attributes in the constructor and common attributes in the stereotype. The ideal approach to inheritance is parasitic composition, in which the [[prototype]] attribute of a subclass points to the parent stereotype and then calls the parent constructor in the subclass constructor to implement the custom inherited parent attribute.
JavaScript objects have always been a bit confusing to me, but I’ll keep exploring. Let me first record what I understand and encourage you. Please kindly point out any mistakes, and I would appreciate your criticism.
This article is the author’s original, please note the link to reprint, the author reserves the right.
Reference: [1] www.cnblogs.com/chuaWeb/p/5… [2] www.cnblogs.com/xjser/p/496… [3] javascriptweblog.wordpress.com/2010/06/07/…