In order to learn javascript in depth and to meet the standards of a standard Web developer, I want to have a deep understanding of object-oriented programming ideas, improve my modular development ability, and write maintainable, efficient and extensible code. I have been reading javascript Design Patterns recently. The key content of the summary and summary, if the summary is not detailed or understanding is not thorough, but also hope the criticism is corrected ~

What is Object-oriented programming (OOP)?

In simple terms, object-oriented programming is abstracting your requirements into an object, then analyzing the object and adding corresponding characteristics (properties) and behaviors (methods) to it. We call this object a class. A very important feature of object-oriented is encapsulation. Although javascript, a weakly typed interpretive language, does not have a special way to achieve the encapsulation of classes like some classical strongly typed languages (such as C++, JAVA, etc.), we can use the flexible characteristics of javascript language to simulate the implementation of these functions. Next, let’s take a look at ~

encapsulation

  • Create a class

It’s easy to create a class in javascript. A common way to create a class is to declare a function to be stored in a variable (usually the first letter of the class name is uppercase), and then add properties or methods to the function (class) by adding properties or methods to this object, for example:

Var Person = function (name, age) {this.name = name; this.age = age; }Copy the code

We can also add properties and methods to the prototype object of the class. There are two ways to add properties and methods to the prototype object of the class.

/ / Person for the prototype object of a class attribute assignment. The prototype. ShowInfo = function () {/ / display information to the console. The log (+ this. 'My name is' name,', I\'m ' + this.age + ' years old! '); } // Assign the object to the class's prototype object. Prototype = {showInfo: Function () {console.log('My name is' + this.name, ', I\'m '+ this.age +' years old! '); }}Copy the code

Now that we have all the attributes and methods we need wrapped in the Person class, we first need to use the new keyword to instantiate (create) the new object. The ~ operator can then use properties or methods that instantiate the object

var person = new Person('Tom',24);
console.log(person.name)        // Tom
console.log(person.showInfo())  // My name is Tom , I'm 24 years old!
Copy the code

We just said there are two ways to add properties and methods. What’s the difference between these two ways?

The properties and methods added by this are added to the current object, whereas the javascript language is prototype-based, pointing to inherited properties and methods from prototype. Methods inherited from Prototype do not belong to the object itself, but are searched through prototype level by level, so that properties or methods defined by this are owned by the object itself. Each time we create a new object using the new operator, The properties and methods referred to by this are also created, but the properties and methods inherited from Prototype are accessed by each object in Prototype. These properties and methods are not created each time a new object is created, as shown below:

Where constructor is a constructor property, a constructor property is created for the prototype object whenever a function or object is created, pointing to the function or object that owns the entire prototype object.

If we add attributes and methods to the prototype object the first way, we get true by executing the following statement:

    console.log(Person.prototype.constructor === Person ) // true
Copy the code

So what happens when I add properties and methods to prototype objects the second way?

    console.log(Person.prototype.constructor === Person ) // false
Copy the code

Oh, my God. What the hell? How did that happen?

The reason for this is that the second method is to assign an entire object to a prototype object, which will overwrite all properties and methods on the prototype object. We need to fix this manually by adding the constructor attribute to the prototype object. Redirect to Person to make sure the prototype chain is correct, i.e. :

    Person.prototype = {
		constructor : Person ,
		showInfo : function() {// Display information console.log()'My name is ' + this.name , ', I\'m ' + this.age + ' years old!'); } } console.log(Person.prototype.constructor === Person ) // trueCopy the code
  • Encapsulation of properties and methods

In most object-oriented languages, attributes and methods of some classes are often hidden and exposed, so there are concepts such as private attributes, private methods, public attributes, and public methods

Before ES6, javascript didn’t have block-level scope. It had function-level scope, which means variables and methods declared inside a function are not accessible from the outside. You can use this feature to simulate creating private variables and methods of a class, while properties and methods created inside a function through this, When a class creates an object, it creates a copy of each object and makes it accessible to the outside world. Therefore, properties and methods created by this can be considered instance properties and methods. However, some methods created by this can access public properties and methods. You can also access the private properties and methods of the class (when created) or the object itself. Properties and methods defined outside the class can only be accessed through the class itself. Therefore, properties and methods defined outside the class are called static public properties and methods of the class. So we call these properties and methods public properties and public methods, also called stereotype properties and methods.

// Create a class var Person =function(name, age) {// Private attribute var IDNumber ='01010101010101010101'; // Private methodsfunction checkIDNumber() {} // The privileged method this.getidNumber =function() {} // Instance attribute this.name = name; this.age = age; // The instance method this.getName =function() {}} // Class static attribute Person.isChinese =true; // class staticMethod person. staticMethod =function () {
            console.log('this is a staticMethod'} // The public attribute person.prototype. isRich =false; / / public methods Person. Prototype. ShowInfo =function () {}
Copy the code

Objects created by new can only access the corresponding instance properties, instance methods, prototype properties and prototype methods, but not the static properties and private properties of a class. The private properties and private methods of a class can only be accessed through the class’s own methods, that is:

        var person = new Person('Tom', 24); console.log(person.IDNumber) // undefined console.log(person.isRich) //false
        console.log(person.name) // Tom
        console.log(person.isChinese) // undefined
        
        console.log(Person.isChinese) // true
        console.log(Person.staticMethod()) // this is a staticMethod
Copy the code
  • Create a safe mode for the object

When we create objects, if we are used to the way jQuery is, we will probably forget to use the new operator when instantiating objects, and instead write the following code:

// Create a class var Person =function (name, age ) {
		this.name = name;
		this.age = age;
	}
	
	var person = Person('Tom'24),Copy the code

At this point, person is no longer an instance of Person

    console.log(person)  // undifined
Copy the code

So the name and age that we created are missing, of course not, they’re hanging onto the window object,

    console.log(window.name)  // Tom
    console.log(window.age)   // 24
Copy the code

Instead of using the new operator to create the object, when the Person method is executed, the function is executed in the global scope, and this points to the global variable, which is the window object, so any properties added will be added to the window, However, when the person variable gets the execution result of Person, because there is no return statement in the function, it returns undipay by default.

To avoid this problem, we can use safe mode to solve it, just tinker with our class,

// Create a class var Person =function(name, age) {// Determine if this is the current object (if true, it was created by new)if ( this instanceof Person ) {
			this.name = name;
			this.age = age;
		} else{// Otherwise recreate the objectreturn new Person(name, age)
		}
	}
Copy the code

Ok, let’s test it now

	var person = Person('Tom'. 24) console.log(person) // Person console.log(person.name) // Tom console.log(person.age) // 24 console.log(window.name)  // undefined console.log(window.age) // undefinedCopy the code

This avoids the problem of forgetting to use new to build instances

Pass: here I use window.name. This property is special. It is used to set or return a string that holds the name of the window

inheritance

Inheritance is also a feature of face objects, but there is no inheritance in the traditional sense in javascript, but we can still use the language features of javascript to simulate the implementation of inheritance

Class type inheritance

More common mode of inheritance, the principle is that we are instantiating a parent class, the newly created object will copy the attributes and methods inside the superclass constructor, and the circular __proto__ refer to the prototype of the parent object, thus has the parent class method on a prototype object and attributes, we assigned to subclasses in the object’s prototype, Then the prototype of the subclass can access the prototype properties and methods of the parent class, and implement inheritance as follows:

// Declare the parent classfunction Super () {
        this.superValue = 'super'; } / / for the parent class to add Super prototype method. The prototype. GetSuperValue =function () {
        returnthis.superValue; } // Declare subclassesfunction Child () {
        this.childValue = 'child'; } // prototype = new Super(); / / to add Child. The prototype method. The prototype getChildValue =function () {
        return this.childValue;
    }
Copy the code

Let’s test it out

    var child = new Child();
    console.log(child.getSuperValue());  // super
    console.log(child.getChildValue());  // child
Copy the code

But this way of inheritance there are two problems, first because the subclass by its prototype prototype of its parent class instantiation, inherit the parent class, as long as there is a reference type in the public attribute of the parent, will be Shared between all instances in the subclass, if one of the subclass changed the superclass constructor reference types of attribute value, will directly affect the other child, Such as:

// Declare the parent classfunction Super() {this.superObject = {a: 1, b: 2}} // Declare subclassesfunction ChildPrototype = new Super(); } var child1 = new Child(); var child2 = new Child(); console.log(child1.superObject); // { a : 1 , b : 2 } child2.superObject.a = 3 ; console.log(child1.superObject); // { a : 3, b : 2 }Copy the code

This will cause a lot of trouble for the subsequent operation!

Second, since the subclass is implemented through the prototype instantiation of the parent class, there is no way to pass parameters to the parent class at the time of the creation of the parent class, so there is no way to initialize the attributes inside the parent class constructor when the parent class is instantiated.

To solve these problems, other inheritance methods are derived.

Constructor inheritance uses the call method to change the context of a function, call the method in a subclass, and execute the variables in the subclass in the parent class. Since the parent class is bound to this, the subclass inherits the instance attributes of the parent class.

// Declare the parent classfunctionSuper (value) { this.value = value; Enclosing superObject = {a: 1, b: 2}} / Super/for the parent class to add the prototype method. The prototype. ShowSuperObject =function() { console.log(this.superValue); } // Declare subclassesfunctionChild(value) {super. call(this,value)} var child1 = new Child('Tom');
    var child2 = new Child('Jack');

    child1.superObject.a = 3 ;
    console.log(child1.superObject);    // { a : 3 , b : 2 }
    console.log(child1.value)           // Tom
    console.log(child2.superObject);    // { a : 1,  b : 2 }
    console.log(child2.value);          // Jack
Copy the code

The super. call(this,value) code is the essence of constructor inheritance, so that class inheritance can be avoided

However, this method does not involve the prototype method, so the parent class of the prototype method will not be inherited, and if you want to be inherited by the subclass, must be put in the constructor, so that each instance created will have a separate copy, cannot be shared, to solve this problem, there is a composite inheritance.

Combinatorial inheritance

We can implement combinatorial inheritance by executing the constructor of the parent class once in the context of the constructor of the child class and instantiating the parent class once in the prototype proroType of the child class:

// Declare the parent classfunctionSuper (value) { this.value = value; Enclosing superObject = {a: 1, b: 2}} / Super/for the parent class to add the prototype method. The prototype. ShowSuperObject =function() { console.log(this.superObject); } // Declare subclassesfunctionCall (this,value)} // prototype = new Super(); var child1 = new Child('Tom');
    var child2 = new Child('Jack');
    
    child1.superObject.a = 3 ;
    console.log(child1.showSuperObject());      // { a : 3 , b : 2 }
    console.log(child1.value)                   // Tom
    child1.superObject.b = 3 ;
    console.log(child2.showSuperObject());      // { a : 1,  b : 2 }
    console.log(child2.value);                  // Jack
Copy the code

This allows you to merge the advantages of class and constructor inheritance and filter out the disadvantages. Look is already very perfect, NO, attentive students can be found, when we use the constructor inherit to perform again the superclass constructor, and in the realization of class type of subclasses prototype inheritance and calls the superclass constructor, then the parent class constructor performed twice, it is can continue to optimize.

Parasitic combinatorial inheritance

We learned about combinatorial inheritance above, and we also saw the disadvantages of this approach, so we derived parasitic combinatorial inheritance, where parasitic inheritance is parasitic inheritance, and parasitic inheritance relies on primary inheritance, so before we learn, we need to know about primary inheritance and parasitic inheritance.

Prototype inheritance is similar to class inheritance, of course, there are the same problems, the code is as follows:

// Original type inheritancefunctionInheritObject (o) {// Declare a transition function objectfunction F() {} // The prototype of the transition object inherits the parent object f.prototype = o; // Returns an instance of the transition object whose prototype inherits the parent objectreturn new F();
    }
    var Super = {
    	name : 'Super' ,
        object : {
    		a : 1 ,
            b : 2
        }
    }
    var child1 = inheritObject(Super);
    var child2 = inheritObject(Super);
    console.log(child1.object) // { a : 1 , b : 2 }
    child1.object.a = 3 ;
    console.log(child2.object) // { a : 3 , b : 2 }
Copy the code

Parasitic inheritance is the second encapsulation of prototype inheritance, and the object is expanded in the encapsulation process. The new object has new attributes and methods, and the implementation method is as follows:

// Original type inheritancefunctionInheritObject (o) {// Declare a transition function objectfunction F() {} // The prototype of the transition object inherits the parent object f.prototype = o; // Returns an instance of the transition object whose prototype inherits the parent objectreturnnew F(); } var Super = {name:'Super' ,
        object : {
    		a : 1 ,
            b : 2
        }
    }
    
    functionCreateChild (obj) {var o = new inheritObject(obj); // expand the new object. Getobject =function () {
            console.log(this.object)
    	}
    	return o;
    }
Copy the code

We combine the characteristics of the two and we get parasitic combinatorial inheritance, inheriting properties by borrowing constructors, inheriting methods through a hybrid form of prototype chains,

/** * Parasitic combination inheritance * pass parameters * childClass subclass * superClass parent ** // primitive inheritancefunctionInheritObject (o) {// Declare a transition function objectfunction F() {} // The prototype of the transition object inherits the parent object f.prototype = o; // Returns an instance of the transition object whose prototype inherits the parent objectreturn new F();
    }
    
    functionVar var p = inheritObject(superClass. Prototype); Constructor p.constructor = childClass; Prototype = p; }Copy the code

We need to inherit the prototype of the parent class, we don’t need to call the constructor of the parent class, we just need a copy of the prototype of the parent class, and this copy we can get through the prototype inheritance, if directly assigned to the child class object, it will cause the prototype of the child class to be confused, Since the constructor object of the parent class is copied to P and does not point to the object of the subclass, it is corrected and assigned to the subclass’s prototype so that the subclass inherits the parent class’s prototype but does not implement the parent class’s constructor.

Ok, test it:

// Define the parent classfunctionSuperClass (name) { this.name = name; Enclosing object = {a: 1, b: 2}} / / SuperClass. The definition of the parent class prototype. The prototype showName =function() {console.log(this.name)} // Define a subclassfunctionChildClass (name,age) {// Constructor extends superclass.call (this,name); // Subclass new attribute this.age = age; } // inheritPrototype(ChildClass,SuperClass); / / subclass ChildClass new prototype method. The prototype. ShowAge =function () {
        console.log(this.age)
    }
    
    //
    var child1 = new ChildClass('Tom', 24); var child2 = new ChildClass('Jack', 25); console.log(child1.object) // { a : 1 , b : 2 } child1.object.a = 3 ; console.log(child1.object) // { a : 3 , b : 2 } console.log(child2.object) // { a : 1 , b : 2 } console.log(child1.showName()) // Tom console.log(child2.showAge()) // 25Copy the code

Now there is no problem. All the previous problems have been solved

Multiple inheritance

Like Java, C++ object-oriented will have your concept of multiple inheritance, but javascript inheritance is dependent on the realization of the prototype chain, but there is only one prototype chain, theoretically can not achieve multiple inheritance. But we can take advantage of the flexibility of javascript to achieve similar multiple inheritance by inheriting properties from multiple objects.

First, let’s take a look at a classic method that inherits single-object properties, extend

Function extend (target,source) {for(var property in source) {// Copy properties from source to target[property] = Source [property]} // Return target; }Copy the code

However, this method is a shallow copy process, which means that only the basic data type can be copied. If the data of the reference type does not achieve the desired effect, data tampering may occur:

var parent = {
	name: 'super',
	object: {
		a: 1,
		b: 2
	}
}

var child = {
	age: 24
}

extend(child, parent);

console.log(child);     //{ age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent);    //{ name: "super", object: { a : 3 , b : 2 } }
Copy the code

To achieve multiple inheritance, it is necessary to copy the attributes of multiple objects to the source object, and then achieve the inheritance of the attributes of multiple objects, we can refer to the extend method in the jQuery framework, modify our above function ~

Function isPlainObject(obj) {var proto, Ctor; // (2) Use object.property.toString to exclude some host objects, such as window, navigator, global if (! obj || ({}).toString.call(obj) ! == "[object Object]") { return false; } proto = Object.getPrototypeOf(obj); // Only objects constructed from {} literals and new objects have their prototype chain null if (! proto) { return true; } / / (1) if the constructor is one of the object has its own properties, it is true Ctor, finally returns false / / (2) the Function prototype. ToString unable to custom, Ctor = ({}).hasownProperty. call(proto, "constructor") &&proto.constructor; return typeof Ctor === "function" && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object); } function extend() { var name, options, src, copy, clone, copyIsArray; var length = arguments.length; Var deep = false; Var I = 1; / / not spreading, parameters of the first case, the default is the first target parameters var target = the arguments [0] | | {}; // If the first argument is a Boolean, the second argument is target if (typeof target == 'Boolean ') {deep = target; target = arguments[i] || {}; i++; } // If target is not an object, we cannot copy it, so we use {} if (typeof target! == "object" && ! ( typeof target === 'function')) { target = {}; } // Loop over the objects to be copied for (; i < length; I ++) {// Get current object options = arguments[I]; // Request cannot be null to avoid extend(a, b) if (options! = null) {for (name in options) {SRC = target[name]; // Copy = options[name]; If (target === copy) {continue; } / / to recursion object must be plainObject or Array if (deep && copy && (isPlainObject (copy) | | (copyIsArray = Array. IsArray (copy)))) {/ / If (copyIsArray) {copyIsArray = false; clone = src && Array.isArray(src) ? src : []; } else { clone = src && isPlainObject(src) ? src : {}; } target[name] = extend(deep, clone, copy); } else if (copy ! == undefined) { target[name] = copy; } } } } return target; };Copy the code

This method defaults to shallow copy, that is:

var parent = {
    name: 'super',
    object: {
        a: 1,
        b: 2
    }
}
var child = {
    age: 24
}

extend(child,parent)
console.log(child); // { age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent) // { name: "super", object: { a : 3 , b : 2 } }
Copy the code

We just need to pass the first argument to true to deep copy, i.e. :

    extend(true,child,parent)
    console.log(child); // { age: 24, name: "super", object: { a : 1 , b : 2 } }
    child.object.a = 3;
    console.log(parent) // { name: "super", object: { a : 1 , b : 2 } }
Copy the code

Ok ~ These are some object-oriented knowledge in javascript, can carefully see the partners here, I believe you have a further understanding and understanding of Object-Oriented programming in javascript, but also laid a foundation for the study of the following design pattern. Next, we will continue to share different design patterns in javascript