js object-oriented and prototype inheritance learning notes.

Posted by Rangel on Sat, 18 May 2019 00:13:47 +0200

create object

Although Object constructors or object literals can be used to create a single object, these methods have obvious drawbacks: creating many objects with the same interface can generate a lot of duplicate code. To solve this problem, people began to use a variant of the factory model.

Factory mode

Factory pattern is a well-known design pattern in the field of software engineering, which abstracts the process of creating concrete objects. Considering that classes cannot be created in ECMAScript, developers have invented a function that encapsulates the details of creating objects with specific interfaces.

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName=function(){
        console.log(this.name)
    }
    return o;
}
var person1 = createPerson('Nicholas',29,'SoftWare');
var person2 = createPerson('gres',27,'Doctor');

The cretePerson() function can construct a Person object with all the necessary information based on the accepted parameters. This function can be called countless times, and each time it returns an object containing three attributes and one method. Although the factory pattern solves the problem of creating multiple similar objects, it does not solve the problem of object recognition.

Constructor Pattern

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
var person1 = new Person('Nicholas',29,'Software');
var person2 = new Person('gres',24,'Docotor');

In this example, the Person() function replaces the createPerson() function. Differences:

1. There is no explicit creation of objects.
2. Assign attributes and methods directly to this object.
3. No return statement.

To create a Person instance, you must use the new operator. Calling a constructor in this way actually goes through the following four steps:

1. Create an object.
2. Assign the scope of the constructor to a new object (so this points to the new object).
3. Execute the code in the constructor. Add attributes to this new object.
4. Return a new object.

The examples above: person1 and person2 hold a different instance of Person, respectively. Both objects have a constructor attribute that points to Person.

person1.constructor == Person//true
person2.constructor == Person//true

person1 instanceof Object;//true
person1 instanceof Person;//true

Creating a custom constructor means that its instances can be marked as a specific type in the future; that's where the construction pattern outperforms the factory pattern. (The constructor defined in this way is defined in the Global object (in the browser, the window object).
The only difference between constructors and other functions is that they are called in different ways. However, constructors are functions after all, and there is no special grammar for defining constructors. Any function can be called as a constructor as long as it is called by the new operator; any function, if not by the new operator, is no different from a normal function.
The problem of constructors:

The main problem with constructors is that each method is recreated on each instance. In the example above, both person1 and person2 have a method called sayName(), but the two methods are not instances of the same Function. In js, functions are also objects, so each function defined is an object instantiated.
function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayName = new Function(console.log(this.name))
}

From this perspective, it is easier to understand the nature of each Perosn instance containing a different Function instance. To be clear, creating functions in this way results in different scoping chains and identifier resolution, but the mechanism for creating new instances of Function remains the same. Therefore, functions of the same name on different instances are not equal.

console.log(person1.sayName==person2.sayName);//false.

However, it is not necessary to create two Function instances that accomplish the same task; furthermore, there are this objects that do not bind functions to specific objects at all before executing code. Therefore, this problem can be solved by transferring the function definition to the outside of the constructor.

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName(){
    console.log(this.name)
}
var person1 = new Person('ll',24);
var person2 = new Person('kk',25);

Archetypal model

Each function we create has a prototype attribute, which is a pointer to an object whose purpose is to contain properties and methods that can be shared by all instances of a particular type.

function Person(){}
Person.prototype.name = 'll';
Person.prototype.age = 24;
Person.prototype.sayName=function(){
    console.log(this.name)
}
var person1 = new Person();
var person2 = new Person();

Here we add sayName() methods and attributes directly to Person's prototype attribute, and the constructor becomes an empty function. Even so, new objects can still be created by calling constructors, and new objects will have the same properties and methods. But unlike the constructor pattern, the attributes and methods of the new object are shared by all instances.
Understanding prototype objects:

Whenever a new function is created, a prototype attribute is created for the function according to certain rules, which points to the prototype object of the function. By default, all prototype objects automatically get a constructor property, which is a pointer to the function where the prototype property resides. Person.prototype.constructor points to Person. With this constructor, we can continue to create other attributes and methods for prototype objects.
When a custom constructor is created, its prototype object will only get the constructor attribute by default; as for other attributes and methods, they are inherited from the Object object. When a new instance of the constructor is called, it will contain a pointer to the prototype object of the constructor. (_proto_); both person1 and person2 contain an internal attribute that only points to Person.prototype and has no direct relationship with the constructor.
Every time the code reads an attribute of an object, it performs a search. The target is an attribute with a given name. The search begins with the object instance itself. If an attribute with a given name is found in the instance, the value of the attribute is returned. If it is not found, it continues to search for the prototype object pointed to by the pointer, and if the attribute is found in the prototype object. Sex returns the value of the property.
function Person(){}
Person.prototype.name = 'll';
Person.prototype.age = 24;
Person.prototype.sayName=function(){
    console.log(this.name)
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'kk';
console.log(person1.name)//kk - from an example
console.log(person2.name)//ll comes from prototype

When an object instance adds an attribute, it masks the same-name attribute stored in the prototype object.

The drawbacks of the prototype model are as follows:

All attributes in the prototype are shared by many instances, which is very suitable for functions. In the past, for those attributes that contained basic values, the corresponding attributes in the prototype could be hidden by adding a property with the same name to the instance. However, for attributes containing reference type values, the problem is more prominent:
function Person(){}
Person.prototype={
    constructor:Person,
    name:'kk',
    age:24,
    friends:['ll','jj'],
    sayName:function(){
        console.log(this.name);
    }
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('aa');
console.log(person1.friends);//ll jj aa
console.log(person2.friends);//ll jj aa
console.log(person1.friends==person2.friends);//true

Modify the array referenced by person1.friends, and person2 will do the same.

Combining constructors and prototypes

The most common way to create custom types is to combine constructors with prototype patterns. The constructor pattern is used to define instance attributes, and the prototype pattern is used to define shared attributes and methods. In addition, this model also supports passing parameters to constructors.

function Person(name,age){
    this.name = name;
    this.age = age;
    this.friends=['kk','ll'];
}
Person.prototype={
    constructor:Person,
    sayName:function(){
        console.log(this.name)
    }
}
var person1 = new Person('nnn',24);
var person2 = new Person('mmm',29);
person1.friends.push('aaa');
console.log(person1.friends);//kk ll aa
console.log(person2.friends);//kk ll 
console.log(person1.friends==person2.friends);//false
console.log(person1.sayName==person2.sayName);//true

Topics: Javascript Attribute ECMAScript