What is object-oriented programming?

Object – oriented programming is a programming idea.

Process-oriented: Analyzing the steps needed to solve a problem, writing functions to implement each step, and then calling the functions in turn. Pay attention to order.

Object oriented: it is to disassemble the things that constitute the problem into various objects, and the purpose of disassembling the objects is not to achieve a certain step, but to describe the behavior of this thing in the current problem.

Object-oriented characteristics

  • Inheritance: To inherit methods and attributes from a parent class for code reuse. Subclasses also have their own attributes
  • Encapsulation: Lets the user not think about the internal implementation, but only about the functional use of the object
  • Different objects acting on the same operation produce different effects. The idea of polymorphism is really to separate “what do you want to do” from “who will do it?

object

Some built-in objects

Object Array Date Function RegExp….

1. Understand objects

Create an object

1. Create an Object instance and add attributes and methods to it

Let person = new Object() person.name = "小 小 "; person.age = "22"; person.sayName = fucntion(){ console.log(this.name) }Copy the code

2 The literal of the object

Function (){consoel.log(this.name)}} let person = {name:" ", age:22, sayName:function(){consoel.log(this.name)}}Copy the code

1. Attribute type

There are two types of properties in JS: data properties and accessor properties.

1.1 Data Attributes

Data attributes have four characteristics that describe behavior

The '64x' signals that the property can be deleted through delete, and the property can be redefined, modified, or modified as an accessor property. 'Enumerable' : indicates whether a property can be returned through a for-in loop or not. 'Writable' : indicates whether the value of an attribute can be modified. 'Value' : indicates the data Value that contains this attribute. When you read a property, you read from this location, and when you write a property value, you save the new value in this location.Copy the code

The [[Erable]],[[Enumerable]],[[Writable]] features of the properties defined directly on the object in the previous example are set to true, and the [[Value]] features are set to the specified Value. Such as:

Let person = {name:" xiaoming "}Copy the code

The object.defineProperty () method must be used. This method takes three parameters: the attributes of the object, attribute name, and a descriptor object, which the descriptor object attribute must be: configurable, enumerable, writable, and value, set up one or more of these values, you can modify the corresponding attributes.

} object.defineProperty (person,'name',{writable:false, Value :" person "}) person. Name = "person" console.log(person. Name) age:18 } Object.defineProperty(person,'name',{ configurable:false, // delete person.name console.log(person) // delete person.name console.log(person) / / {name: "little red", the age: 18}Copy the code

Moreover, once a property is defined as unconfigurable, it cannot be changed back to configurable. Calling object.defineProperty () to modify features other than writable results in an error:

let person = {}; Object.defineProperty(person,'name',{ configurable:false, DefineProperty (person,'name',{64x :true, works without any additional information. Value :" 64X "}) // The same property can be modified by calling the Object.defineProperty() method multiple times, but any additional information is limited if the property of the 64X is set to false.Copy the code

1.2 Accessor Properties

Getter and setter functions

When the accessor property is read, the getter function is called, which returns a valid value; When the accessor property is written, a setter function is called and the new value is passed in. This function is responsible for how the data is processed.

Accessor properties have the following four properties:

The '64x' signals that the property can be deleted through delete and can be redefined, the features of the property can be modified, or the property can be changed to a data property. The default value for this feature is true for properties defined directly on an object. 'Enumerable' : Indicates whether you can return a property through a for-in loop. The default value for this feature is true for properties defined directly on an object. 'Get' : a function called when a property is read. The default value is undefined. 'Set' : function to be called when an attribute is written. The default value is undefined.Copy the code
Let book = {_year: 2004, // used to represent attributes that can only be accessed through object methods. edition: 1 }; Object.defineProperty(book, "year", { get: function () { return this._year; }, set: function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; }}}); book.year = 2005; Set console.log(book.edition) //2Copy the code

1.3 Defining multiple attributes

Object.defineproperties () method: A way for an Object to define multiple properties, using the descriptor to define multiple properties at once. This method takes two object parameters: the first object is the object whose properties are to be added and modified, and the properties of the second object correspond to the properties to be added or modified in the first object.

let book = {}; Object.defineProperties(book, { _year: { value: 2004 }, edition: { value: 1 }, year: { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; }}}}); book.year = 2005; console.log(book.edition); //1 console.log(book.year); / / 2004Copy the code

1.4 Features of Reading Attributes

Object. GetOwnPropertyDescriptor () method

Can get a descriptor for a given property. This method takes two parameters: the object on which the property is located and the name of the property whose descriptor is to be read. The return value is an object, and if it is an accessor property, the object properties are Different, Enumerable, GET, and set. If the object is a data property, the properties of this object are different, Enumerable, Writable, and Value.

let book = {}; Object.defineProperties(book, { _year: { value: 2004, writable:true }, edition: { value: 1 }, year: { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; }}}}); book.year = 2005; / / data attributes let descriptor = Object. GetOwnPropertyDescriptor (book, '_year) console. The log (value) descriptor. / / 2004 console.log(descriptor.configurable) //false console.log(descriptor.writable) //true console.log(descriptor.enumerable) //false console.log(typeof Descriptor. Get) //undefined // Accessor property let SecondDescriptor = Object.getOwnPropertyDescriptor(book,'year') console.log(typeof seconddescriptor.get) //function console.log(seconddescriptor.enumerable) //false console.log(seconddescriptor.value) //undefinedCopy the code

Create an object

Although Object constructors or Object literals can be used to create a single Object, there is a significant drawback to these approaches: creating many objects using the same interface creates a lot of duplicate code.

2.1 Factory Mode

function creatobj(name,age,year){
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.year = year;
    obj.sayName = function(){
        console.log(this.name)
    }
    return obj
}

let person1 = creatobj('xiaoming',18,1997)

console.log(person1)
person1.sayName()

let person2 = creatobj('xiaohuang',20,2000)
Copy the code

The creatobj() function builds an obj object that contains all the necessary information based on the parameters it receives. This function can be called countless times, and each time it returns an object containing a method with three properties. While the factory pattern solves the problem of creating multiple similar objects, it does not solve the problem of object recognition (how to know the type of an object).

2.11 Advantages and disadvantages of factory mode

Advantages: The factory pattern solves the problem of creating multiple similar objects. (I.e., lots of redundant code that repeats itself)

Disadvantages: Does not solve the problem of object recognition (how to know the type of an object). The type of the instance is Object, but not creatobj.

2.2 Constructor pattern

Constructors can be used to create objects of a specific type. Native constructors, such as Object and Array, automatically appear in the execution environment at runtime. In addition, you can create custom constructors to define properties and methods of a custom object type.

function Creatobj(name,age,year){ this.name = name; this.age = age; This.sayname = function(){console.log(this.name)}} let person = new Creatobj('xiaoming',18,2000) person.sayname () //xiaoming console.log(person.name) //xiaoming let person2 = new Creatobj('xiaohong',20,1998)Copy the code

In this example, the Createobj() function replaces the Createobj() function. We notice that the code in Createobj() has the following differences besides replacing the same parts in Createobj() :

  • No object is explicitly created;
  • Assign attributes and methods directly to this object;
  • No return statement.

It is also worth noting that constructor names generally start with a capital letter, such as Createobj().

2.2.1 New keyword

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

  1. Create a new object (for example, P1)

  2. Set the __proto__ attribute of the new object to the function’s prototype (that is, p1.__proto__ === fn.prototype)

  3. Point the function’s this to the newly created object

  4. Return a new object

    1. The constructor returns no explicit valuethis
    2. Constructors have an explicit return value and are primitive types, such asnumber.stringSo return this
    3. The constructor has an explicit return value and is an object type, such as{a:1}, so return this object

Please refer to the interview folder for specific packaging process.

At the end of the previous example, person1 and person2 each hold a different instance of Creatobj. Both objects have a constructor property that points to Creatobj, as shown below

console.log(person.constructor === Creatobj)   //true
console.log(person2.constructor === Creatobj)  //true
Copy the code

The created Object is both an instanceof Object and an instanceof Creatobj. This can be verified by instanceof:

console.log(person instanceof Creatobj)  //true
console.log(person2 instanceof Creatobj)  //true
Copy the code

2.2.2 Treating constructors as functions

The only difference between constructors and other functions is the way they are called.

Any function called with the new operator can be used as a constructor; Any function that is not called by the new operator is just like a normal function.

Use the previous example as a constructor:

var person  = new Creatobj("xiaokong",22);
person.sayName();   //xiaokong
Copy the code

Call as a normal function:

Creatobj("xiaokong",22);
window.sayName();   //"xiaokong"
Copy the code

Call from another object’s scope:

let obj = new Object();
Creatobj.call(obj,"xiaokong",22)
obj.sayName()   //"xiaokong"
Copy the code

The first two lines of code in this example show a typical use of constructors, using the new operator to create a new object. The next two lines of code show what happens when Creatobj() is called without the new operator: properties and methods are added to the window object. As you may recall, when a function is called in the Global scope, this always points to the Global object (the window object in the browser). Therefore, after calling the function, you can call the sayName() method from the window object and return “xiaokong”. Finally, you can also use call() (or apply()) to call the Creatobj() function in the scope of a particular object. This is called in the scope of the object obj, so obj has all the attributes and the sayName() method.

2.2.3 Advantages and disadvantages of the constructor mode

Advantages: Even if you change the properties or methods of one object, it does not affect other objects (because each object is a copy), eliminating the inability to determine the type in factory mode.

Disadvantages: The main problem with constructors is that each method has to be recreated on each instance (a copy in memory), and methods cannot be reused across instances, resulting in a waste of memory

2.3 Prototype Mode

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

Prototype is the prototype object of the object instance created by calling the constructor.

The advantage of using a prototype object is that all object instances can share the properties and methods it contains. In other words, instead of defining information about an object instance in a constructor, you can add that information directly to the prototype object.

function Person(){}

Person.prototype.name = 'xiaoming'
Person.prototype.age = 18
Person.prototype.sayName = function(){
    console.log(this.name)
}

let person1 = new Person()
person1.sayName()  //'xiaoming'

let person2 = new Person()
person2.sayName()  //'xiaoming'

console.log(person1.sayName === person2.sayName)  //true
Copy the code

We added the sayName() method and all the properties directly to the Person prototype property, leaving the constructor empty. Even so, you can still create a new object by calling the constructor, and the new object will have the same properties and methods. But unlike the constructor pattern, these properties and methods of the new object are shared by all instances. In other words, person1 and person2 access the same set of attributes and the same sayName() function.

2.3.1 Understand prototype objects

Whenever a new function is created, a Prototype property is created for the function according to a specific set of rules, which points to the function’s prototype object.

By default, all prototype objects automatically get a constructor property that contains a pointer to the function of the Prototype property.

(the Person. The prototype. The constructor to Person)

With this constructor, we can continue to add other properties and methods to the prototype object.

After a custom constructor is created, its prototype object acquires only the constructor attribute by default; As for the other methods, they are inherited from Object.

When a constructor is called to create a new instance, the inside of the instance contains a pointer (inner property) to the constructor’s prototype object.

Using the previous code that created instances using the Person constructor and Person.prototype, we can show the relationships between objects:

Pointed to the prototype object Person. The prototype, and the Person. The prototype. The constructor refers back to the Person. In addition to the constructor attribute, the prototype object contains other attributes that were added later. Each instance of Person — person1 and person2 contains an internal property that only points to Person.prototype; In other words, they are not directly related to constructors.

  • IsPrototypeOf () method

Although [[Prototype]] is not accessible in any implementation, you can use the isPrototypeOf() method to determine whether this relationship exists between objects. Essentially, this method returns true if [[Prototype]](Prototype pointer) points to the object (Person.prototype) on which isPrototypeOf() was called.

console.log(Person.prototype.isPrototypeOf(person1))  //true

console.log(Person.prototype.isPrototypeOf(person2))  //true
Copy the code

Person1 and person2 return true because they both have an internal pointer to Person.prototype.

  • Object.getPrototypeOf()

This method returns the value of [[Prototype]].

console.log(Object.getPrototypeOf(person1) === Person.prototype) //true console.log(Object.getPrototypeOf(person2).name)  //"xiaoming" console.log(Object.getPrototypeOf(person1)) //Person.prototypeCopy the code

The first line of code here simply confirms that the Object returned by Object.getProtoTypeof () is actually the prototype of the Object.

The second line of code retrieves the value of the name property in the prototype object, which is “xiaoming”.

Using Object.getProtoTypeof () makes it easy to get a prototype of an Object.

Every time the code reads an attribute of an object, a search is performed for the attribute with the given name.

The search starts with the object instance itself.

If an attribute with the given name is found in the instance, the value of that attribute is returned;

If not, search continues for the prototype object to which the pointer points, looking for the property with the given name in the prototype object.

If the property is found in the stereotype object, the value of the property is returned.

Although you can access values stored in the stereotype through the object instance, you cannot override values in the stereotype through the object instance

. If we add a property in the instance that has the same name as a property in the instance stereotype, we create that property in the instance, and that property will mask the property in the stereotype.

function Person(){}; Person.prototype.name = "xiaoming"; Person.prototype.age = "22"; Person.prototype.job = "haha"; Person.prototype.sayName = function(){ console.log(this.name); } var person1 = new Person(); person1.name = "huahua"; console.log(person1.name); Var person2 = new Person(); console.log(person2.name); // When "xiaoming" comes from the prototype //, we need to read the value of person1.name, so we search for an attribute named name on this instance. // The attribute does exist, so return its value without searching for the stereotype. // When person2.name is accessed in the same way, the attribute is not found on the instance, so the search for the stereotype continues and the name attribute is found there.Copy the code

When an attribute is added to an object instance, the attribute hides the same property stored in the prototype object. In other words, adding this property will only prevent us from accessing that property in the prototype, but not modifying that property. Even if this property is set to NULL, it will only be set in the instance and its connection to the stereotype will not be restored. However, using the DELETE operator allows instance attributes to be completely removed, allowing us to revisit the attributes in the stereotype.

function Person(){} Person.prototype.name = "xiaoming"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "huahua"; console.log(person1.name); //"huahua" -- from instance console.log(person2.name); //"xiaoming" -- from the prototype delete person1.name; console.log(person1.name); //"xiaoming" -- from the prototypeCopy the code

In this modified example, we used the delete operator to remove person1.name, which previously held a value of “huahua” that masked the stereotype attribute of the same name. Once it is removed, the connection to the name property in the stereotype is restored. Therefore, the next call to person1.name returns the value of the name attribute in the stereotype.

  • HasOwnProperty properties

You can detect whether a property exists in an instance or in a stereotype.

This method (don’t forget that it inherits from Object) returns true only if the given property exists in the Object instance.

function Person(){}

Person.prototype.name = 'xiaoming'
Person.prototype.age = 16
Person.prototype.sayName = function(){
    console.log(this.name)
}

let person1 = new Person()
person1.sayName()

let person2 = new Person()
person2.sayName()


console.log(person1.hasOwnProperty("name"))  //false

person2.name = 'xiaohong'
console.log(person2.hasOwnProperty("name"))  //true

delete person2.name
console.log(person2.hasOwnProperty("name"))  //false
Copy the code

When person1.hasownProperty (“name”) is called, true is returned only if Person1 overrides the name property, because name is an instance property, not a stereotype property.

Object. GetOwnPropertyDescriptor () method can only be used for instance attributes, in order to get a prototype property descriptor, must call directly on the prototype Object Object. GetOwnPropertyDescriptor () method.

let des = Object.getOwnPropertyDescriptor(person2,"name")
console.log(des) //{ value: 'xiaoming', writable: true, enumerable: true, configurable: true }


let des2 = Object.getOwnPropertyDescriptor(person1,"name")
console.log(des2) //undefined

let des3 = Object.getOwnPropertyDescriptor(Person.prototype,"name")
console.log(des3) //{ value: 'xiaoming',writable:true,enumerable: true,configurable: true}
Copy the code

2.3.2 Prototype and in operator

There are two ways to use the IN operator: alone and in for-in loops. When used alone, the IN operator returns true if a given property is accessible through an object, whether it exists in an instance or stereotype.

function Person(){}

Person.prototype.name = 'xiaoming'
Person.prototype.age = 16
Person.prototype.sayName = function(){
    console.log(this.name)
}

let person1 = new Person()
let person2 = new Person()

person2.name = "xiaohong"

console.log(person1.hasOwnProperty("name"))  //false
console.log(person2.hasOwnProperty("name"))  //true

console.log("name" in person1)  //true
console.log("name" in person2)  //true
Copy the code

According to the characteristics of IN, the encapsulation method can be used to determine whether it can be accessed on the prototype:

function hasPrototypeProperty(object , name){ return ! object.hasOwnPrototype(name) && name in object; }Copy the code

Use:

console.log(hasPrototypeProperty(person1,"name"))  //true
console.log(hasPrototypeProperty(person2,"name"))  //false
Copy the code
  • Object. The key () method

To get all enumerable instance properties on an Object, use the object.keys () method. This method takes an object as an argument and returns an array of strings containing all the enumerable properties.

function Person(){}

Person.prototype.name = 'xiaoming'
Person.prototype.age = 16
Person.prototype.sayName = function(){
    console.log(this.name)
}

let person1 = new Person()
let person2 = new Person()

person2.name = "xiaohong"
person2.age = 18


console.log(Object.keys(person2))  //[ 'name', 'age' ]
console.log(Object.keys(Person.prototype))  //[ 'name', 'age', 'sayName' ]
Copy the code
  • Object.getOwnPropertyNames()

Gets all instance attributes, whether or not they are enumerable.

let keys = Object.getOwnPropertyNames(Person.propertype)

console.log(keys) //[ 'name', 'age', 'sayName' ]
Copy the code

2.3.3 Simpler prototype syntax

Type Person.prototype every time you add a property or method in the previous example. To reduce unnecessary input, and to better visually encapsulate the functionality of a stereotype, it is more common to rewrite the entire stereotype object with an object literal containing all properties and methods.

function Person(){}

Person.prototype = {
    name:"xiaoming",
    age:18,
    sayName:function(){
        console.log(this.name)
    }
}
Copy the code

We set Person.prototype to be equal to a new object created as an object literal. The end result is the same, with one exception: the constructor property no longer points to Person.

As mentioned earlier, every time a function is created, its Prototype object is created, which also automatically acquires the constructor attribute. The syntax we use here essentially overrides the default Prototype Object so that the constructor property becomes the constructor property of the new Object (pointing to the Object constructor) instead of pointing to the Person function.

At this point, although the instanceof operator still returns the correct result, constructor no longer determines the type of the object, as shown below:

Person.prototype = { name:"xiaoming", age:18, sayName:function(){ console.log(this.name) } } let first = new Person() console.log(first.constructor == Person) console.log(first.constructor == Object) console.log(first instanceof Person) console.log(first instanceof Object) // First instanceof Person indicates whether Person.prototype exists on the prototype chain of the first parameter.Copy the code

If the constructor value is really important, you can deliberately set it back to the appropriate value as follows.

Person.prototype = { constructor:Person, name:"xiaoming", age:18, SayName :function(){console.log(this.name)}} // The above code purposely includes a constructor attribute and sets its value to Person, thus ensuring that the appropriate value can be accessed from this attribute.Copy the code

Note that resetting the constructor property in this way causes its [[Enumerable]] property to be set to true. By default, the native constructor property is not enumerable, so if you’re using an ECMAScript 5-compliant JavaScript engine, try the object.defineProperty () method.

Person.prototype = {
    name:"xiaoming",
    age:18,
    sayName:function(){
        console.log(this.name)
    }
}

Object.definePorperty(Person.prototype,'constructor',{
    Enumerable:false,
    value:Person
})
Copy the code

2.3.4 Dynamic nature of prototype

Because finding the value in the prototype is a search, any changes we make to the prototype object are immediately reflected in the instance — even if we create the instance first and then modify the prototype.

var friend = new Person();

Person.prototype.sayHa = function(){
    console.log("hahaha");
}
friend.sayHa();               //"hahaha"
Copy the code

The above code starts by creating an instance of Person and saving it in Person. The next statement then adds a method sayHa() to Person.prototype.

Even if the Person instance was created before the new method was added, it can still access the new method. The reason can be attributed to the loose connection between instance and prototype. When we call Person.sayha (), we first search the instance for an attribute named sayHa, and if none is found, we continue to search the prototype.

Because the connection between the instance and the stereotype is just a pointer, not a copy, you can find the new sayHa property in the stereotype and return the function stored there.

But if you’re rewriting the entire prototype object, it’s a different story. As we know, calling the constructor adds a [[Prototype]] pointer to the original Prototype to the instance, and changing the Prototype to another object breaks the link between the constructor and the original Prototype.

Remember: Pointers in instances point only to prototypes, not to constructors.

Function Person(){} var friend = new Person(); Person. Prototype = {constructor: Person, name: "xiaoming", age: 29, job: "Software Engineer", sayName : function () { console.log(this.name); }}; friend.sayName(); //errorCopy the code

In this example, we created an instance of Person and then overwrote its prototype object. Then an error occurs when you call friend.sayname () because the prototype to which friend points does not contain an attribute named by that name.

Overriding the stereotype object disconnects the existing stereotype from any pre-existing object instance; They still refer to the original prototype.

Conclusion: There are two major problems with adding properties to a function’s prototype property by rewriting the prototype: 1. The overwritten constructor property is missing and no longer points to the constructor by default. 2. If the prototype property of the function is overridden after the instance is created, the proto of the instance is also lost

2.3.5 Prototype of native objects

All native reference types (Object, Array, String, and so on) define methods on archetypes of their constructors.

For example, you can find the sort() method in array.prototype, and the substring() method in String.prototype, as follows:

console.log(typeof String.prototype.substring); //"function" console.log(typeof Array.prototype.sort); //"function" // the method defined by prototype in the native reference typeCopy the code

With a prototype of a native object, you can not only get references to all default methods, but also define new methods. You can add a method called startsWith() as modified from String.

Modify the prototype of the native object as you define the prototype of the object, so you can add methods at any time. The following code gives the basic wrapper type.

let str = '123456' String.prototype.mystartWith = function(text){ return this.indexOf(text) === -1 } Console. log(str.mystartWith(2)) //false console.log(str.mystartwith (7)) //true // Checks whether a character exists in the stringCopy the code
function Person(){}

Person.prototype = {
    name:"xiaoming",
    age:18,
    friends:['xiaohua','xiaowan','mingming'],
    sayName:function(){
        console.log(this.name)
    }
}


let first = new Person()
let second = new Person()

second.friends.pop();

console.log(first.friends)  //[ 'xiaohua', 'xiaowan' ]
console.log(second.friends)  //[ 'xiaohua', 'xiaowan' ]
Copy the code

As shown above: if a reference type attribute is defined, all instances will share the same attribute, and if one instance changes, all will change.

2.3.6 Pros and cons of prototype mode

Advantages: All parameters and methods are created in memory only once, and instantiated objects point to the Prototype object.

Disadvantages: 1. All attributes in the stereotype are shared by many instances. For attributes that reference a type value, adding or modifying an attribute in one instance will change other instances. 2. Passing initialization parameters to the constructor is omitted, resulting in all instances taking the same property values by default.

2.4 Use a combination of constructor and stereotype patterns

function Person(name,age,friends){
    this.name = name;
    this.age = age;
    this.friends = friends
}

Person.prototype = {
    constructor:Person,
    sayName(){
        console.log(this.name)
    }
}

let person1 = new Person('xiaoming',18,['xiaowang','xiaolu','xiangfen'])
let person2 = new Person('xiaohong',20,['xiaoyi','xiaohei','xiangwu'])

person1.friends.pop();
console.log(person1.friends)  //[ 'xiaowang', 'xiaolu' ]
console.log(person2.friends)  //[ 'xiaoyi', 'xiaohei', 'xiangwu' ]

console.log(person1.name === person2.name)  //true
console.log(person1.sayName === person2.sayName)  //false
Copy the code

Instance properties are defined in the constructor, while the constructor property and method sayName(), shared by all instances, are defined in the stereotype. Changing person1.friends (adding a new string to it) does not affect person2.friends because they reference different arrays.

2.5 Dynamic prototyping mode

It encapsulates all the information in the constructor, while preserving the advantage of using both the constructor and the stereotype by initializing the stereotype in the constructor (only when necessary). In other words, you can determine whether a stereotype needs to be initialized by checking whether a method that should exist is valid.

function Person(name,age,friends){ this.name = name; this.age = age; this.friends = friends if(typeof this.sayName ! = 'function'){ Person.prototype.sayName = function(){ console.log(this.name) } } } let person1 = new Person('xiaoming',18,['xiaohong','xiaolu','xiaohuang']) let person2 = new Person('xiaohong',20,['xiaozi','xiaohua']) person1.sayName() //xiaomingCopy the code

2.6 The parasitic constructor pattern

This pattern is exactly the same as the factory pattern, except that we use the new operator and call the wrapper function we use a constructor. By default, the constructor returns a new object instance without a value. By adding a return statement to the end of the constructor, you can override the value returned when the constructor is called.

function Person(name,age,friends){
    const obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.friends = friends;
    obj.sayName = function(){
        console.log(this.name)
    }

    return obj
}

let person1 = new Person('xiaoming',18,['xiaohua','xianghuang','xiaolu'])
let person2 = new Person('xiaohua',20,['xiaofen','xianggua','xiaotu'])

person2.sayName()  //xiaohua
console.log(person1.age)  //18
console.log(person1.sayName === person2.sayName)  //false
Copy the code

2.7 Secure constructor pattern

We can rewrite the previous function as:

Function Person(name, age, job){// Create the Object to return var o = new Object(); O.sayname = function(){console.log(name); }; // return object return o; }Copy the code

Note that in objects created in this pattern, there is no way to access the value of name other than using the sayName() method. You can use the prudent Person constructor as follows.

var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
Copy the code

Thus, the friend variable holds a safe object, and there is no other way to access its data members than by calling the sayName() method. Even if other code adds methods or data members to the object, there is no other way to access the raw data passed into the constructor.

3. Inheritance

3.1 Inheritance of prototype chain

The basic idea is to use stereotypes to make one reference type inherit the properties and methods of another.

Constructor, instance, and stereotype relationships:

Each constructor has a prototype object, which contains a pointer to the constructor, and each instance contains an internal pointer to the prototype object.

If the stereotype is equal to an instance of another type, then the stereotype will contain a pointer to another stereotype, which in turn will contain a pointer to another constructor. If another prototype is an instance of another type, then the above relationship still holds, and so on, the chain of instance and prototype is formed. This is the concept of prototype chain.

That is, if an instance of a stereotype with a pointer to a stereotype is equal to a stereotype object, then the stereotype object has a pointer to that stereotype.

function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } // SuperType subtype.prototype = new SuperType(); Add a method to the prototype of the subclass after the inheritance relationship is established. Because is essentially rewrite the subType. Prototype subType. Prototype. GetSubValue = function () {return this. Subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); The code above defines two types: SuperType and SubType. Each type has an attribute and a method. The main difference is that SubType inherits from SuperType, which is achieved by creating an instance of SuperType and assigning that instance to subType.prototype. The essence of the implementation is to rewrite the prototype object and replace it with an instance of a new type. In other words, all the properties and methods that existed in the SuperType instance now also exist in subType.prototype. After the inheritance relationship is established, we add a method to subtype. prototype, which adds a new method to the properties and methods inherited from SuperType.Copy the code

In the above code, instead of using the default stereotype provided by SubType, we replace it with a new stereotype; This new prototype is an instance of SuperType. As a result, the new stereotype not only has all the properties and methods that it has as an instance of SuperType, but also has a pointer inside that points to the SuperType stereotype. The end result is this: Instance points to the prototype of SubType, which in turn points to the prototype of SuperType. The getSuperValue() method is still in superType.prototype, but the property is in subtype.prototype. This is because Property is an instance property and getSuperValue() is a prototype method. Since subType. prototype is now an instance of SuperType, the property is of course in that instance. Also, note that instance.constructor now points to SuperType because the constructor from subtype. prototype was overwritten.

3.1.1 Default stereotypes

All reference types inherit from Object by default, and this inheritance is implemented according to the stereotype chain. The default prototype for all functions is an instance of Object, so the default prototype contains an internal pointer to Object.prototype. This is why all custom types inherit default methods like toString(), valueOf(), etc.

SubType inherits from SuperType, which inherits from Object. When instance.tostring () is called, you are actually calling the method stored in Object.prototype.

3.1.2 Determine the relationship between prototype and instance

  • instanceofmethods
    console.log(instance instanceof Object);   //true
    console.log(instance instanceof SuperType);    //true
    console.log(instance instanceof SupType);  //true
Copy the code
  • isPrototypef()methods
    console.log(Object.prototype.isPrototypeOf(instance))  //true
    console.log(SuperType.prototype.isPrototypeOf(instance))  //true
    console.log(SubType.prototype.isPrototypeOf(instance))  //true
Copy the code

3.1.3 Careful definition method

Subtypes sometimes require overwriting a method in the supertype, or adding a method that does not exist in the supertype. However, the code that adds methods to the stereotype must come after the statement that replaces the stereotype.

function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } // SuperType subtype.prototype = new SuperType(); / / add new methods Again after the inheritance to add a new method, otherwise it will be overwritten SubType. Prototype. GetSubValue = function () {return enclosing subproperty; }; / / rewriting methods on the supertype SubType. Prototype. GetSuperValue = function () {return false. }; var instance = new SubType(); console.log(instance.getSuperValue()); //falseCopy the code

Object literals cannot be used to create stereotype methods when inheritance is implemented through stereotype chains. Because doing so would rewrite the prototype chain.

function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } // SuperType subtype.prototype = new SuperType(); Subtype. prototype = {getSubValue: function (){return this.subproperty; }, someOtherMethod : function (){ return false; }}; var instance = new SubType(); console.log(instance.getSuperValue()); //error!Copy the code

The code above shows the problem of just assigning an instance of SuperType to a stereotype and then replacing the stereotype with an object literal. Since the prototype now contains an instance of Object rather than SuperType, the chain of stereotypes we envisioned has been severed — there is no relationship between SubType and SuperType.

3.2 Constructor inheritance

function SuperType(){
    this.colors = ['red','blue','yellow','pink'];
}

function SubType(){
    SuperType.call(this)
}

let person1 = new SubType()
let person2 = new SubType()

person1.colors.push('write')

console.log(person1.colors) //[ 'red', 'blue', 'yellow', 'pink', 'write' ]
console.log(person2.colors) //[ 'red', 'blue', 'yellow', 'pink' ]
Copy the code

By using the call() method (or apply(), we are actually calling the SuperType constructor in the context of a newly created SubType instance that will be created in the future. This will execute all the object initialization code defined in the SuperType() function on the new SubType object. As a result, each instance of SubType has its own copy of the Colors attribute.

3.2.1 Transfer Parameters

function SuperType(name){ this.name = name; } function SubType(name){// SuperType. Call (this, name); // Instance attribute this.age = 29; } let instance = new SubType('Nicholas'); console.log(instance.name); //"Nicholas"; console.log(instance.age); / / 29Copy the code

The SuperType in the code above takes only one parameter, name, which is assigned directly to an attribute. When you call the SuperType constructor from within the SubType constructor, you actually set the name attribute for the instance of the SubType. To ensure that the SuperType constructor does not override the attributes of a subtype, you can add attributes that should be defined in the subtype after calling the SuperType constructor.

3.3 Combinatorial Inheritance

Inheritance of stereotype properties and methods is implemented using stereotype chains, while inheritance of instance properties is implemented by borrowing constructors. In this way, function reuse is achieved by defining methods on prototypes, while ensuring that each instance has its own attributes.

function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType. Prototype. SayName = function () {the console. The log (enclosing name)} function SubType (name, age) {/ / inherited the SuperType, Supertype.call (this, name) is also passed; // The instance attribute this.age = age; } SubType.prototype = new SuperType() SubType.prototype.sayAge = function(){ console.log(this.age) } let instance1 = new  SubType('Nicholas',29); instance1.colors.push("black"); console.log(instance1.colors); //[ 'red', 'blue', 'green', 'black' ] instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 let instance2 = new SubType('boer',18) console.log(instance2.colors); //[ 'red', 'blue', 'green' ] instance2.sayName(); //"boer"; instance2.sayAge(); / / 18Copy the code

The instanceof operator tests whether an object has a constructor’s Prototype property in its prototype chain.

The isPrototypeOf() method tests whether an object exists on the prototype chain of another object.

3.4 Original type inheritance

This approach is not strictly constructors; the idea is that you can use stereotypes to create new objects based on existing objects without having to create custom types.

function object(o){
    function F(){};
    F.prototype = o;
    return new F()
}
Copy the code

Inside the object() function, a temporary constructor is created, the passed object is used as a prototype of the constructor, and a new instance of the temporary type is returned. In essence, object() makes a shallow copy of the object passed into it.

let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Copy the code

It requires that you have one object that can be used as a basis for another. If such an object exists, you can pass it to the object() function and modify the resulting object as needed. In this case, the person object is the basis for another object, so we pass it into the object() function, which returns a new object. This new object uses Person as a stereotype, so its stereotype contains a primitive type value attribute and a reference type value attribute. This means that Person. friends is not only owned by person, but also shared by anotherPerson and yetAnotherPerson. In effect, this is the equivalent of creating two more copies of the Person object.

ECMAScript5 normalizes primitive inheritance by adding the object.create () method. This method takes two parameters: an object to be used as a prototype for the new object and, optionally, an object that defines additional properties for the new object. Object.create() behaves the same as the Object () method when passed a parameter.

let person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Copy the code

The second argument to the object.create () method has the same format as the second argument to the Object.defineProperties() method: each property is defined by its own descriptor. Any property specified in this way overrides the property of the same name on the prototype object. Such as:

let person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = Object.create(person, {
    name: {
    value: "Greg"
    }
});
console.log(anotherPerson.name); //"Greg"
Copy the code

3.5 Parasitic inheritance

The idea of parasitic inheritance is similar to that of the parasitic constructor and factory pattern, which is to create a function that simply encapsulates the inheritance process, enhances the object internally in some way, and finally returns the object as if it had really done all the work.

function createAnother(original){ var clone = object(original); SayHi = function(){// Somehow enhance this object console.log("hi"); }; return clone; // Return this object}Copy the code

In this example, the createAnother() function takes an argument, which is the object that will be the basis for the new object. This object(Original) is then passed to the object() function, which assigns the result to Clone. Add a new method sayHi() to the Clone object and return it. You can use createAnother() like this:

var person={ name:"Nicholas", friends:["Shelby","Court","Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"Copy the code

The code in this example returns a new object, anotherPerson, based on Person. The new object not only has all the attributes and methods of Person, but also has its own sayHi() method.

3.6 Parasitic combinatorial inheritance

Composite inheritance is the most common inheritance pattern in JavaScript. However, it has its own disadvantages. The biggest problem with composite inheritance is that in any case, the supertype constructor is called twice: once when the subtype stereotype is created and once inside the subtype constructor. Yes, subtypes eventually contain all of the instance properties of the supertype object, but we have to override those properties when we call the subtype constructor. Take a look at the following composite inheritance example:

function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); // Call this.age = age; } SubType.prototype = new SuperType(); / / the first call to SubType. The prototype. The constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); };Copy the code

When the SuperType constructor is first called, subtype. prototype gets two attributes: name and colors; They are all instance properties of SuperType, but are now in the stereotype of SubType. When the SubType constructor is called, the SuperType constructor is called again, this time creating the instance attributes name and colors on the new object. Thus, these two properties mask the two properties of the same name in the stereotype.

Implementation idea:

Instead of calling the supertype constructor to specify the stereotype of the subtype, all we need is a copy of the supertype stereotype. Essentially, you use parasitic inheritance to inherit the stereotype of the supertype and then assign the result to the stereotype of the subtype.

Function inheritPrototype(subType, superType){let prototype = object(supertype.prototype); Prototype. constructor = subType; // Add object subtype. prototype = prototype; // create a class with no instance methods let Super = function(){}; Super.prototype = superType.prototype; // Subtype.prototype = new Super(); Prototype = object.create (supertype.prototype);Copy the code

This function takes two arguments: a subtype constructor and a supertype constructor. Inside the function, the first step is to create a copy of the supertype stereotype. The second step is to add the Constructor attribute to the created copy to make up for the loss of the default constructor attribute by rewriting the stereotype. Finally, the newly created object (that is, the copy) is assigned to the prototype of the subtype. We can replace the inheritPrototype() assignment in the previous example with a call to the inheritPrototype() function, for example:

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"]; 
} 
SuperType.prototype.sayName = function(){
    aleconsole.log(this.name); 
}; 
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age; 
} 
inheritPrototype(SubType, SuperType); 

SubType.prototype.sayAge = function(){
    console.log(this.age); 
};
Copy the code

The efficiency of this example is that it calls the SuperType constructor only once and thus avoids creating unnecessary, redundant attributes on subType. prototype. At the same time, the prototype chain stays the same; Therefore, instanceof and isPrototypeOf() can also be used normally.

Conclusion:

Summary:

ECMAScript supports object-oriented (OO) programming, but does not use classes or interfaces. Objects can be created and enhanced during code execution and therefore have dynamic rather than strictly defined entities. In the absence of classes, the following pattern can be used to create objects.

  • Factory mode, which uses simple functions to create objects, add properties and methods to them, and then return them. This pattern was later replaced by the constructor pattern.

  • Constructor pattern, you can create custom reference types, and you can use the new operator just as you would create built-in object instances. However, the constructor pattern also has the disadvantage that every member of it cannot be reused, including functions. Because functions are not limited to any object (that is, they are loosely coupled to objects), there is no reason not to share functions across multiple objects.

  • The prototype pattern, which uses the constructor’s Prototype property to specify which properties and methods should be shared. When you combine constructor and stereotype patterns, you use constructors to define instance properties and stereotypes to define shared properties and methods.

JavaScript implements inheritance primarily through prototype chains. The chain of stereotypes is built by assigning an instance of a type to a stereotype of another constructor. In this way, the subtype has access to all the properties and methods of the supertype, much like class-based inheritance. The problem with stereotype chains is that object instances share all inherited properties and methods, and therefore are not suitable for use alone. The technique to solve this problem is to borrow the constructor, which calls the supertype constructor inside the subtype constructor. This allows each instance to have its own attributes, while ensuring that only the constructor pattern is used to define types. The most commonly used inheritance pattern is composite inheritance, which inherits shared properties and methods using stereotype chains and instance properties by borrowing constructors.

In addition, there are the following alternative inheritance patterns.

  • Primitive inheritance, which can be implemented without predefined constructors, is essentially a shallow copy of a given object. The resulting copy can be further modified.

  • Parasitic inheritance, very similar to primitive inheritance, creates an object based on an object or some information, enhances the object, and returns the object. To address the inefficiency of the composite inheritance pattern due to multiple calls to supertype constructors, this pattern can be used with composite inheritance.

  • Parasitic combinatorial inheritance, which combines the advantages of both parasitic inheritance and combinatorial inheritance, is the most effective way to implement type-based inheritance.