This article teaches you what an object is, constructor, 5 modes for creating an object, 5 ways to implement inheritance, shallow copy, etc. This article is accompanied by a free instructional video called Object-oriented Programming. Thank you for your attention – thumbs-up – favorites, the follow-up will continue to update quality content.

JavaScript statements have strong object-oriented programming capabilities

What is the object

Object Oriented Programming (OOP) is the mainstream Programming paradigm. It abstracts all kinds of complex relationships in the real world into objects, and then completes the simulation of the real world through the division of labor and cooperation between objects.

Each object is a function center with a clear division of labor, which can complete tasks such as receiving information, processing data and sending information. Objects can be reused and customized through inheritance mechanisms. Therefore, object-oriented programming is flexible, reusable, highly modular and easy to maintain and develop. Compared with traditional procedural programming composed of a series of functions or instructions, it is more suitable for large-scale software projects with multiple people.

So what exactly is an object? We understand it on two levels.

[1] An object is an abstraction of a single object

A book, a car, a person can all be objects, as can a database, a web page, a connection to a remote server. When objects are abstracted into objects, the relationship between objects becomes the relationship between objects, so that the real situation can be simulated and the object can be programmed.

[2] Object is a container that encapsulates properties and methods.

Properties are the state of the object, and methods are the behavior of the object (to accomplish something). For example, we can abstract an animal as an animal object, using “attributes” to record which animal it is, and “methods” to represent certain behaviors of the animal (running, hunting, resting, etc.).

The constructor

The first step in object-oriented programming is to generate objects. As I said, an object is an abstraction of a single object. You usually need a template that represents common characteristics of a class of objects from which objects are generated.

Typical object-oriented programming languages, such as C++ and Java, have the concept of a class. A class is a template of an object, and an object is an instance of a class. However, the object architecture of the JavaScript language is not based on “classes”, but on constructors and prototype chains.

The JavaScript language uses constructors as templates for objects. A “constructor” is a function specifically used to generate instance objects. It is the template of the object and describes the basic structure of the instance object. A constructor that generates multiple instance objects, all of which have the same structure.

A constructor is an ordinary function, but it has its own characteristics and uses.

var Dog = function(){
    this.name = 'huang';
}
Copy the code

In the code above, Dog is the constructor. To distinguish constructor names from normal functions, the first letter is usually capitalized.

Constructors have two characteristics:

  • It’s used in the body of the functionthisKeyword that represents the object instance to be generated.
  • When generating objects, you must use the new command

Constructors can take arguments as needed

function Dog (name){
    this.name = name;
}
var d1 = new Dog('huang');
console.log(d1.name);/ / huang
Copy the code

If you forget to use the new operator, this will represent the global object Window

function Dog(){
    this.name = name;
}
var d1 = Dog();
//Uncaught TypeError: Cannot read property 'name' of undefined
console.log(d1.name);
Copy the code

In the code above, forgetting to use the new command actually causes D1 to program undefined and the name attribute to become a global variable. Therefore, you should be very careful not to call the constructor directly without using the new command

To ensure that the constructor must be used with the new command, one solution is to use strict mode inside the constructor, with use strict on the first line. That way, if you forget to use the new command, calling the constructor directly will return an error.

function Dog(name){
    'use strict';
    this.name = name;
}
var d1 = Dog('huang');
Copy the code

Dog in the code above is the constructor, and the use strict command ensures that this function runs in strict mode. Because in strict mode, this inside a function cannot refer to a global object and is equal to undefined by default, the call without new will result in an error (JavaScript does not allow adding attributes to undefined).

instanceof

The operator runtime indicates whether the object is an instance of a particular class

As an alternative, the constructor internally checks whether the new command is used and returns an instance object if it is not

The instanceof operator can be used to identify the type of an object

function Dog(name){
    if(! (this instanceof Dog)){
        return new Dog(name);
    }
    this.name = name;
}
var d1 = Dog('huang');
console.log(d1.name);/ / 'huang'
console.log(Dog('huang').name);/ / 'huang'
Copy the code

The constructor in the above code, with or without the new command, yields the same result

The new command

As you can see, if we want to create an object, after we declare the constructor, we must use the new command to instantiate the object. So let’s look at how the new command works

When the new command is used, the functions that follow it perform the following steps

  1. Creates an empty object as an instance of the object to be returned
  2. Points the empty object’s prototype to the constructor’sprototypeattribute
  3. Assigns this empty object to the internalthisThe keyword
  4. Start executing the code inside the constructor

That is, inside the constructor, this refers to a newly generated empty object on which all operations will be performed. A constructor is called a “constructor” because its purpose is to manipulate an empty object (this object) and “construct” it into what it needs to look like.

constructor

Each object is automatically created with a constructor attribute, Contructor, that contains a reference to its constructor. This Constructor property actually inherits from the stereotype object, and constructor is the only property of the stereotype object

function Dog(){}var d1 = new Dog();
console.log(d1.constructor === Person);//true
console.log(d1.__proto__.constructor === Person);//true
Copy the code

Here are the internal properties of Dog, and found that Constructor is an inherited property

While there is such a relationship between object instances and their constructors, you can use Instanceof to check object types.

The return value

The return statement in a function is used to return the value of the call, whereas the return value of the new constructor is a bit special.

If the constructor uses a return statement but does not specify a return value, or if the return value is a primitive value, the return value is ignored and the new object is used as the result of the call

function Fn(){
    this.a = 2;
    return;
}
var test = new Fn();
console.log(test);//{a:2}
Copy the code

If the constructor explicitly returns an object using a return statement, the value of the calling expression is that object

var obj = {a:1};
function fn(){
    this.a = 2;
    return obj;
}
var test = new fn();
console.log(test);//{a:1}
Copy the code

The advantage of using constructors is that all objects created using the same constructor have the same properties and methods

function Person(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name); }}var p1 = new Person('Tom');
var p2 = new Person('Jack');
Copy the code

Constructors allow objects to be configured with the same attributes, but constructors do not eliminate code redundancy. The main problem with using constructors is that each method has to be recreated on each instance. In the example above, each object has its own sayName() method. This also means that if there are 100 object instances, there are 100 functions doing the same thing, just using different data.

function Person(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name); }}var p1 = new Person('Tom');
var p2 = new Person('Jack');
console.log(p1.sayName === p2.sayName);//false
Copy the code

In the code above, p1 and p2 are two instances of a constructor with the sayName method. Since the sayName method is generated on each instance object, the two instances are generated twice. That is, every time an instance is created, a new sayName method is created. This is unnecessary and wasteful of system resources, so all sayName methods behave the same and should be shared entirely.

The solution to this problem. This is the JavaScript prototype object

A prototype object

When we talk about prototype objects, we talk about the triangulation of prototype objects, instance objects, and constructors

The following two lines of code illustrate their relationship

function Foo(){};
var f1 = new Foo();
Copy the code

The constructor

The function used to initialize the newly created object is the constructor. In this example, the Foo function is the constructor

Instance objects

The objects created by the constructor’s new operation are instance objects, often referred to as object instances. Multiple instance objects can be constructed using a single constructor. The following f1 and F2 are instance objects

function Foo(){};
var f1 = new Foo();
var f2 = new Foo();
console.log(f1 === f2);//false
Copy the code

Prototype object and Prototype

After the instance object is created through the constructor’s new operation, the constructor automatically creates a Prototype property that points to the prototype object of the instance object. Multiple objects instantiated through the same constructor have the same prototype object. In this example, foo. prototype is the prototype object

function Foo(){};
Foo.prototype.a = 1;
var f1 = new Foo;
var f2 = new Foo;

console.log(Foo.prototype.a);/ / 1
console. log(f1.a);/ / 1
console.log(f2.a);/ / 1
Copy the code

The prototype property

The JavaScript inheritance mechanism is designed with the idea that all properties and methods of the prototype object can be shared by the instance object. That is, if properties and methods are defined on the stereotype, then all instance objects can be shared, saving memory and showing the relationship between instance objects.

How to specify a prototype for an object. JavaScript states that each function has a Prototype property that points to an object

function fn(){};
// The fn function has a prototype attribute by default and refers to an object
console.log(typeof fn.prototype);//"Object"
Copy the code

For ordinary functions, this property is basically useless. But in the case of constructors, this property automatically becomes the prototype of the instance object when the instance is generated.

function  Person(name){
    this.name = name;
}
Person.prototype.age = 18;
var p1 = new Person('the king');
var p2 = new Person('two Kings');
console.log(p1.age);/ / 18
console.log(p2.age);/ / 18
Copy the code

In the code above, the Person constructor’s prototype property is the prototype object for instance objects P1 and p2. An age attribute is added to the prototype object, and as a result, the instance objects share this attribute.

Properties of the stereotype object are not properties of the instance object itself. As soon as the prototype object is modified, the change is immediately reflected in all instance objects

Person.prototype.age = 40;
console.log(p1.age);/ / 40
console.log(p2.age);/ / 40
Copy the code

In the code above, the age property of the prototype object changes to 40, which immediately changes the age property of the two instances. This is because the instance object doesn’t actually have an age property, but instead reads the age property of the prototype object. That is, when the instance object doesn’t have a property or method, it goes to the prototype object to find that property or method. This is what makes prototype objects special.

If the instance object has a property or method, it will not go to the prototype object to find that property or method

p1.age = 35;
console.log(p1.age);/ / 35
console.log(p2.age);/ / 40
console.log(Person.prototype.age) / / 40
Copy the code

In the code above, the age property of instance object P1 is changed to 35, so that it no longer reads the age property of the prototype object, which still has the value of 40.

conclusion

The purpose of a stereotype object is to define properties and methods shared by all instance objects. This is why it is called a prototype object, and instance objects can be considered children of the prototype object

Person.prototype.sayAge = function(){
    console.log('My age is'+ this.age);
}
Copy the code

In the code above, the Person. Prototype object defines a sayAge method that can be called on all Person instance objects.

Prototype chain

JavaScript dictates that all objects have their own prototype object. On the one hand, any object can serve as a prototype for other objects. On the other hand, since a prototype object is also an object, it also has its own prototype. Therefore, a prototype chain is formed: the prototype of the object, then the prototype of the prototype……

The prototype property of the Object constructor is called Object. Prototype. That is, all objects inherit the properties of Object.prototype. This is why all objects have valueof and toString methods, because these are inherited from Object.prototype.

Does the object. prototype Object have a prototype? Object. Prototype is null. Null has no properties, no methods, and no prototype of its own. Thus, the end of the prototype chain is NULL.

Object.getPrototypeOf(Object.prototype);//null
Copy the code

Prototype is null. Null has no attributes, so the prototype chain ends there. The object.getProtoTypeof method returns the prototype of the parameter Object, which is described in this lesson on Object methods.

When reading a property of an object, the JavaScript engine looks for the property of the object itself; if it can’t find it, it looks for its prototype; if it still can’t find it, it looks for the prototype. If no Object. Prototype is found up to the top level, undefined is returned. Now, if an object itself and its archetype both define a property of the same name, we can look at attributes of the object itself first, and that’s called Overriding.

Note that looking for a property one level up the entire prototype chain has an impact on performance. The higher the level of the prototype object whose properties are sought, the greater the impact on performance. If you look for an attribute that doesn’t exist, the entire prototype chain will be traversed.

For example, having the prototype property point to an array means that the instance object can call array methods

var MyArray = function () {};

MyArray.prototype = Array.prototype;
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1.2.3);
mine.length / / 3
mine instanceof Array // true
Copy the code

constructor

By default, the stereotype object only gets the constructor property, pointing to the corresponding constructor of the stereotype object. The other methods are inherited from Object

function Foo(){};
console.log(Foo.prototype.constructor === Foo);//true
Copy the code

Since an instance object can inherit properties from a stereotype, it also has a constructor property that points to the corresponding stereotype object’s constructor

function Foo(){};
var f1 = new Foo();
console.log(f1.constructor === Foo);//true
console.log(f1.constuctor === Foo.prototype.constructor);//true
f1.hasOwnProperty('constructor');//false
Copy the code

Code above, the f1 is the constructor Foo instance of the object, but there is no constructor f1 itself attribute, this property is read on the prototype chain of Foo prototype. The constructor property

The constructor property lets you know which constructor generated an instance object.

function Foo(){};
var f1 = new Foo();
console.log(f1.constructor === Foo);//true
console.log(f1.constructor === Array);//false
Copy the code

The constructor property represents the association between the stereotype object and the constructor. If the stereotype object is modified, the constructor property is generally modified at the same time to prevent errors when referencing it

Here’s an example:

function Person(name){
    this.name = name;
}
console.log(Person.prototype.constructor === Person);//true

// Modify the prototype object
Person.prototype = {
    fn:function(){}};console.log(Person.prototype.constructor === Person);//false
console.log(Person.prototype.constructor === Object);//true
Copy the code

Therefore, when modifying a prototype object, you generally modify the point to the constructor property as well

function Person(name){
    this.name = name;
}
console.log(Person.prototype.constructor === Person);//true

// Modify the prototype object
Person.prototype = {
    constructor:Person,
    fn:function(){
        console.log(this.name); }};var p1 = new Person('huang');
console.log(p1 instanceof Person);//true
console.log(Person.constructor == Person);//true
console.log(Person.constructor === Object);//false
Copy the code

__proto__

The instance object contains a __proto__ attribute inside, pointing to the prototype object corresponding to the instance object

function Foo(){};
var f1 = new Foo;
console.log(f1.__proto__ === Foo.prototype);//true
Copy the code

conclusion

The relationship between constructors, stereotype objects, and instance objects is that there is no direct connection between instance objects and constructors

function Foo(){};
var f1 = new Foo();
Copy the code

The prototype object for the above code is foo. prototype, the instance object is f1, and the constructor is Foo

The relationship between prototype objects and instance objects

console.log(Foo.prototype === f1.__proto__);//true
Copy the code

The relationship between prototype objects and constructed objects

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

Whereas there is no direct relationship between instance objects and constructors, the indirect relationship is that the instance object can inherit the constructor property of the prototype object

console.log(f1.constructor === Foo);//true
Copy the code

If the connection between an instance object and a constructor is anything to go by, it is this: the instance object is the result of the constructor’s new operation

var f1 = new Foo;
Copy the code

After this code is executed, if you reset the prototype object, you break the relationship between the three

function Foo(){};
var f1 = new Foo;
console.log(Foo.prototype === f1.__proto__);//true
console.log(Foo.prototype.constructor === Foo);//true

Foo.prototype = {};
console.log(Foo.prototype === f1.__proto__);//false
console.log(Foo.prototype.constructor === Foo);//false
Copy the code

So, code order is important

Five modes for creating objects

How to create objects, or how to create objects more elegantly. Starting with the simplest way to create objects, we will step through the pattern of creating objects in 5

Object literals

In general, we create an object using the literal form of the object

There are three ways to create objects, including the new constructor, the Object direct, and the object.create () function

[1] New constructor

Use the new operator followed by the Object constructor to initialize a newly created Object

var person = new Object(a); person.name ='mjj';
person.age = 28;
Copy the code

[2] Object literals

JavaScript provides shortcuts to literals for creating most native object values. Using literals simply hides the same basic process as the new operator, so it can also be called syntactic sugar

var person = {
    name:'mjj';
    age:28
}
Copy the code

Objects are defined using the method of object literals, and the property names are automatically converted to strings

【 3 】 Object. The create ()

A common way to generate instance objects is to make the constructor return an instance using the new command. But a lot of times, you can only get one instance object, it might not be generated by the constructor at all, so can you generate another instance object from one instance object?

ES5 defines a method called Object.create() to satisfy this requirement. The method takes an object as an argument and returns an instance object based on it. This instance fully inherits the properties of the prototype object.

// Prototype objects
var A = {
    getX:function(){
        console.log('hello'); }};// Instance object
var B = Object.create(A);
console.log(B.getX);//"hello"
Copy the code

In the code above, the object. create method generates an Object B from an Object A. B inherits all of A’s properties and methods.

var person1 = {
    name:'mjj'.age:28.sayName: function(){
        alert(this.name); }}Copy the code

If we were to create a large number of objects, we would look like this

var person1 = {
    name:'mjj'.age:28.sayName: function(){
        alert(this.name); }}var person2 = {
    name:'alex'.age:38.sayName: function(){
        alert(this.name); }}/* var person3 = {} var person4 = {} var person5 = {} ...... * /
Copy the code

Although object literals can be used to create a single object, creating multiple objects creates a lot of repetitive code

The factory pattern

To solve the above problems, people began to use the factory model. This pattern abstracts the process of creating concrete objects, encapsulating the details of creating objects with specific interfaces with functions

function createPerson(name,age){
    var p = new Object(a); p.name = name; p.age = age; p.sayName =function(){
        alert(this.name);
    }
    return p;
}
var p1 = createPerson('mjj'.28);
var p2 = createPerson('alex'.28);
var p3 = createPerson('huang'.8);
Copy the code

While the factory pattern solves the problem of creating multiple similar objects, it does not solve the problem of object recognition because the type of the object is not given using the pattern

Constructor pattern

You can define properties and methods of a custom object type by creating custom constructors. Creating a custom constructor means that instances of it can be identified as a specific type, which is where the constructor pattern trumps the factory pattern. Instead of explicitly creating an object, the pattern assigns properties and methods directly to this without a return statement

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("mjj".28);
var person2 = new Person("alex".25);
Copy the code

The main problem with using constructors is that each method has to be recreated on each instance, making it unnecessary to create multiple methods that accomplish the same task, wasting memory space

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        alert(this.name);
    };
}
var p1 = new Person("mjj".28);
var p2 = new Person("alex".25);
// The same sayName() method occupies different memory space in both instances p1 and p2
console.log(person1.sayName === person2.sayName);//false
Copy the code
The constructor extends the schema

Moving method definitions outside of constructors, based on the constructor pattern, solves the problem of methods being created repeatedly

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
}
var p1 = new Person("mjj".28);
var p2 = new Person("alex".25);
console.log(person1.sayName === person2.sayName);//true
Copy the code

Now, a new problem arises. Functions defined in a global scope can really only be called by an object, which makes the global scope a bit of a misnomer. Moreover, if the object needs to define many methods, it will define many global functions, which will pollute the global space, and the custom reference type will not be encapsulated

Parasitic constructor pattern

The basic idea of this pattern is to create a function that simply encapsulates the code that created the object and then returns the newly created object. This pattern is a combination of the factory pattern and the constructor pattern

The parasitic constructor pattern has the same problem as the constructor pattern; each method is recreated on each instance, and creating multiple methods to accomplish the same task is unnecessary and wastes memory space

function Person(name,age){
    var p = new Object(a); p.name = name; p.age = age; p.sayName =function(){
        alert(this.name);
    }
    return p;
}
var p1 = new Person('mjj'.28);
var p2 = new Person('alex'.28);
The sayName() method, which has the same function, occupies different memory space in person1 and person2 instances
console.log(p1.sayName === p2.sayName);//false
Copy the code

Another problem is that there is no relationship between the object returned using this pattern and the constructor. Therefore, using the instanceof operator and the Prototype attribute doesn’t make sense. Therefore, this pattern should be avoided as much as possible

function Person(name,age){
    var p = new Object(a); p.name = name; p.age = age; p.sayName =function(){
        alert(this.name);
    }
    return p;
}
var p1 = new Person('mjj'.28);
console.log(p1 instanceof Person);//false
console.log(p1.__proto__ === Person.prototype);//false
Copy the code
Secure constructor pattern

A secure object is one that has no public properties and a method that does not reference this object. Secure objects are best used in secure environments that prohibit the use of this and new or when preventing data from being altered by other applications

The secure constructor is similar to the parasitic constructor pattern, except that the newly created object instance method does not reference this. The second is that the new operator does not apply to calling the constructor.

function Person(name,age){
    // Create the object to return
    var p = new Object(a);// Private variables and functions can be defined here
    // Add method
    p.sayName = function (){
        console.log(name);
    }
    // Return the object
    return p;
}
// There is no other way to access the value of name other than using the sayName() method in secure mode created objects
var p1 = Person('mjj'.28);
p1.sayName();//"mjj"
Copy the code

Like the parasitic constructor pattern, objects created using the secure constructor pattern have no relationship to the constructor, so the instanceof operator has no meaning for such objects. Right

The prototype pattern

With a stereotype object, you can share its properties and methods with all instances. 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 = "mjj";
    Person.prototype.age = 29;
    Person.prototype.sayName = function(){
        console.log(this.name); }}var p1 = new Person();
p1.sayName();//"mjj"
var p2 = new Person();
p2.sayName();//"mjj"
alert(p1.sayName === p2.sayName);//true
Copy the code
Simpler prototype patterns

To reduce unnecessary input, and to better visually encapsulate the functionality of the prototype, rewrite the entire prototype object with an object literal containing methods of all attributes

However, after rewriting the object literal, constructor no longer points to Person. So this method overrides the default Prototype Object completely, leaving the constructor property of Person.prototype out of the way and finding the constructor property of Object.prototype from the prototype chain

function Person(){};
Person.prototype = {
    name:'mjj'.age:28.sayName:function(){
        console.log(this.name); }}var p1 = new Person();
p1.sayName();//"mjj"
console.log(p1.constructor === Person);//false
console.log(p1.constructor === Object);//true
Copy the code

The constructor property of the prototype object can be explicitly set

function Person(){};
Person.prototype = {
    constructor:Person,
    name:'mjj'.age:28.sayName:function(){
        console.log(this.name); }}var p1 = new Person();
p1.sayName();//"mjj"
console.log(p1.constructor === Person);//true
console.log(p1.constructor === Object);//false
Copy the code

The problem with the stereotype pattern is that the reference type value attributes are shared and modified by all instance objects, which is why the stereotype pattern is rarely used alone.

function Person(){};
Person.prototype = {
    constructor:Person,
    name:'mjj'.age:28.friends: ['alex'.'huang'].sayName:function(){
        console.log(this.name); }}var p1 = new Person();
var p2 = new Person();
p1.friends.push('black o');
alert(p1.friends);//[' Alex ',' Huang ',' Hei ']
alert(p2.friends);//[' Alex ',' Huang ',' Hei ']
alert(p1.friends === p2.friends);//true
Copy the code

Portfolio model

Using a combination of constructor and stereotype patterns is the most common way to create custom types. The constructor pattern is used to define instance properties, while the stereotype pattern is used to define method and shared properties, and the combined pattern also supports passing parameters to the constructor. Instance objects have their own copy of instance attributes and share references to methods, maximizing memory savings. This pattern is currently the most widely used and recognized pattern for creating custom objects

function Person(name,age){
    this.name = name;
    this.age = age;
    this.friends = ['alex'.'huang'];
}
Person.prototype = {
    constructor:Person,
    sayName:function(){
        console.log(this.name); }}var p1 = new Person('mjj'.28);
var p2 = new Person('jjm'.30);
p1.friends.push('wusir');
alert(p1.friends);/ / [' alex ', 'huang', 'wusir]
alert(p2.friends);/ / / 'alex', 'huang'
alert(p1.friends === p2.friends);//false
alert(p1.sayName === p2.sayName);//true
Copy the code
Dynamic prototype pattern

The dynamic stereotype pattern encapsulates the constructor and the stereotype object used separately in the composite pattern, and then decides whether to initialize the stereotype object by checking that the method has been created

Using this approach, separate constructors and prototype objects are merged together, resulting in cleaner code and less contamination of global controls

Note: If the prototype object contains more than one statement, you only need to check one of the statements

function Person(name,age){
    / / property
    this.name = name;
    this.age = age;
    / / method
    if(typeof this.sayName ! ="function"){
        Person.prototype.sayName = function(){
            console.log(this.name); }}}var p1 = new Person('Little Pony'.28);
p1.sayName();//
Copy the code

conclusion

Starting with creating one object in literal form, creating multiple objects creates code redundancy; The factory pattern can solve this problem, but it has the problem of object recognition. Then the constructor pattern is introduced, which solves the problem of object recognition, but has the problem of method duplication. Then it introduces the prototype pattern, which is characterized by sharing, but leads to the problem that reference type value attributes will be shared and modified by all instance objects. Finally, the constructor and stereotype composite pattern is proposed. The constructor pattern is used to define instance properties, while the stereotype pattern is used to define methods and shared properties. This composite pattern also supports passing parameters to constructors, which is the most widely used pattern

The object-oriented tabbed case

The effect

Procedure oriented TAB implementation

HTML part

<! DOCTYPEhtml>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>01 Common mode TAB implementation</title>
        <style type="text/css">* {padding: 0;
                margin: 0;
            }
            a{
                text-decoration: none;
            }
            body{
                background-color: #BAA895;
            }
            #wrap{
                width: 302px;
                height: 400px;
                margin: 100px auto;
            }
            ul{
                list-style: none;
                overflow: hidden;
                border: 1px solid #3081BF;
                height: 45px;
                width: 300px;
            }
            ul li{
                float: left;
                width: 100px;
                height: 45px;
                line-height: 45px;
                text-align: center;
            }
            ul li a{
                display: inline-block;
                width: 100px;
                height: 100%;
                font-size: 18px;
                color: # 262626;
            }
            ul li.active{
                background-color:  #3081BF;
                font-weight: bold;
            }
            .content{
                width: 300px;
                height: 300px;
                border: 1px solid  #3081BF;
                display: none;
            }
        </style>
    </head>
    <body>
        <div id="wrap">
            <ul>
                <li class="active">
                    <a href="javascript:void(0);">recommended</a>
                </li>
                <li>
                    <a href="javascript:void(0);">A novel</a>
                </li>
                <li>
                    <a href="javascript:void(0);">navigation</a>
                </li>
            </ul>
            <div class="content"  style="display:block;">recommended</div>
            <div class="content">A novel</div>
            <div class="content">navigation</div>
        </div>
        <script type="text/javascript">
            window.onload = function(){
                // 1. Obtain the required label
               var tabLis = document.getElementsByTagName('li');
               var contentDivs=document.getElementsByClassName('content');
                for(var i = 0; i < tabLis.length; i++){
                    // Save each I
                    tabLis[i].index = i;
                    tabLis[i].onclick = function(){
                        for(var j = 0; j < tabLis.length; j++){ tabLis[j].className =' ';
                            contentDivs[j].style.display = 'none';
                        }
                        this.className  = 'active';
                        contentDivs[this.index].style.display = 'block'; }}}</script>

    </body>
</html>
Copy the code

Slowly change to an object-oriented format

Encapsulation: Separation of functions and methods

window.onload = function(){
    // 1. Obtain the required label
    var tabLis = document.getElementsByTagName('li');
    var contentDivs = document.getElementsByClassName('content');
    for(var i = 0; i < tabLis.length; i++){
        // Save each I
        tabLis[i].index = i;
        tabLis[i].onclick = clickFun;
    }
    function clickFun(){
        for(var j = 0; j < tabLis.length; j++){ tabLis[j].className =' ';
            contentDivs[j].style.display = 'none';
        }
        this.className  = 'active';
        contentDivs[this.index].style.display = 'block'; }}Copy the code

Based on object-oriented to achieve

Ideas:1.Create a constructor for TabSwitch2.Add attributes to the current object (state: bound HTML elements, for example)3.Add methods to the prototype object of the current object (click Methods)Copy the code

window.onload = function(){
    // 1. Create constructor
    function TabSwitch(obj){
        console.log(obj);
        // 2. Bind instance properties
        this.tabLis = obj.children[0].getElementsByTagName('li');
        this.contentDivs = obj.getElementsByTagName('div');
        for(var i = 0; i < this.tabLis.length; i++){
            // Save each I
            this.tabLis[i].index = i;
            this.tabLis[i].onclick = this.clickFun;
        }

    }
    TabSwitch.prototype.clickFun = function(){
        // delete all
        for(var j = 0; j < this.tabLis.length; j++){this.tabLis[j].className = ' ';
            this.contentDivs[j].style.display = 'none';
        }
        this.className  = 'active';
        this.contentDivs[this.index].style.display = 'block';		

    }
    var wrap = document.getElementById('wrap');
    var tab = new TabSwitch(wrap);

}
Copy the code

When you feel like you’ve written it perfectly, you run it on the web and you get an error

This is because clickFun now points to the currently clicked Li tag, and we want this in this method to point to a TAB object.

Put the clickFun call in a function so that the clickFun object does not change. At the same time, also have another problem, at this point the clickFun this points to the TAB object, but this. The className, enclosing the index, the object of this should point to the TAB, so not the two attributes of the TAB object. That’s why the following modification is correct

// 1. Create the TabSwitch constructor
function TabSwitch(id){
    / / save this
    var _this = this;
    var wrap = document.getElementById(id);
    this.tabLis = wrap.children[0].getElementsByTagName('li');
    this.contentDivs = wrap.getElementsByTagName('div');
    for(var i = 0; i< this.tabLis.length; i++){
        // Set the index
        this.tabLis[i].index = i;
        // Add an event to the button
        this.tabLis[i].onclick = function(){
            _this.clickFun(this.index); }}}// Prototype method
TabSwitch.prototype.clickFun = function(index){
    // delete all
    for(var j = 0; j < this.tabLis.length; j++){this.tabLis[j].className = ' ';
        console.log(this.contentDivs)
        this.contentDivs[j].style.display = 'none';
    }
    this.tabLis[index].className  = 'active';
    this.contentDivs[index].style.display = 'block';	
};
new TabSwitch('wrap');
Copy the code

The final version

Extract the code into a separate JS file and import it when you use it

Five ways to implement inheritance

Learning how to create objects is the first step to understanding object-oriented programming. Have you learned that? Object creation is described in detail in the above article. If you don’t have a thorough understanding of it, you are advised to go back and learn something new. So the second part is understanding inheritance. Inheritance means that all properties and methods in the stereotype object can be shared by the instance object. That is to say, we only need to make some modifications on the basis of the original object to get a new object.

Prototype chain inheritance

JavaScript uses prototype chains as the primary method for implementing inheritance, which essentially overwrites the prototype object and replaces it with an instance of a new type. In the following code, the properties and methods that once existed in the SuperType instance object now also exist in subType.prototype

function Super(){
    this.value = true;
}
Super.prototype.getValue = function(){
    return this.value
}
function Sub(){};
//Sub inherits Super
Sub.prototype = new Super();
Sub.prototype.constroctor = Sub;

var ins = new Sub();
console.log(ins.getValue());//true
Copy the code

The above code specifies two types: Super and Sub. Sub inherits Super, which is achieved by creating an instance of Super and assigning it to sub. prototype. The essence of the ** implementation is to rewrite the object and replace it with a property of a new type. ** In other words, all properties and methods that used to exist in instances of Super now also exist in sub.prototype. As shown in the figure.

As you can see from the image above, instead of using the default prototype provided by Sub, we replaced it with a new one; This new prototype is an example of Super. Thus, the new stereotype not only has the properties and methods that it has as an instance of Super, but it also points to the stereotype of Super. The end result looks like this:

Ins =>Sub prototype =>Super prototypeCopy the code

The getValue() method is still in sub.prototype, but the value property is in sub.prototype. This is because value is an instance property and getValue() is a prototype method. Since sub. prototype is now an instance of Super, value is in that instance.

Also, note that ins.constructor now points to Super because the original sub. prototype constructor was overwritten.

The main problem with stereotype chains is that private stereotype attributes are shared by instances, which is why you define attributes in constructors rather than stereotype objects. When inheritance is implemented through stereotypes, the stereotype instance becomes an instance of another type. Thus, the original instance property becomes the prototype property of course.

function Super(){
    this.colors = ['red'.'green'.'blue'];
}
Super.prototype.getValue = function(){
    return this.colors
}
function Sub(){};
//Sub inherits Super
Sub.prototype = new Super();
var ins1 = new Super();
ins1.colors.push('black');
console.log(ins1.colors);//['red','green','blue','black'];
var ins2 = new Sub();
console.log(ins2.colors);//['red','green','blue','black'];
Copy the code

The second problem with stereotype chains is that you cannot pass arguments to the constructor of the parent type when creating an instance of a subtype. In fact, there is no way to pass arguments to the constructor of the parent type without affecting all instances. Coupled with the problem that stereotype attributes containing reference type values are shared by all instances, stereotype chain inheritance alone is rarely used in practice

Pay attention to the problem

Methods are defined carefully with stereotype chain inheritance. Subtypes sometimes need to override a method of the parent class, or add a method that doesn’t exist in the parent class. However, the code that adds methods to the stereotype must come after the statement that replaces the stereotype.

function Super() {
    this.colors = ['red'.'green'.'blue'];
}
Super.prototype.getValue = function() {
    return this.colors
}

function Sub() {
    this.colors = ['black'];
};
//Sub inherits Super
Sub.prototype = new Super();

// Adding an existing method to the parent overrides the parent method
Sub.prototype.getValue = function() {
    return this.colors;
}
// Add a method that does not exist in the parent class
Sub.prototype.getSubValue = function(){
    return false;
}
var ins = new Sub();
// The result of overwriting the parent method
console.log(ins.getValue()); //['black']
// The result of the newly defined method in the subclass
console.log(ins.getSubValue());//false
// The parent class calls getValue() with the same value
console.log(new Super().getValue());//['red', 'green', 'blue']

Copy the code

Borrow constructor inheritance

The technique of borrowing constructors (sometimes called pseudo-class inheritance or classical inheritance). The basic idea of this technique is fairly simple: call the superclass constructor inside the subclass constructor. Remember that functions are simply objects that execute code in a particular environment, so you can also execute constructors on newly created objects by using the Apply () and call() methods.

function Super() {
    this.colors = ['red'.'green'.'blue'];
}
Super.prototype.getValue = function(){
    return this.colors;
}
function Sub(){
    // Inherits Super
    Super.call(this);// Replace this in the constructor Super with an INS instance object, so that only the private properties defined in Super are inherited, not the public methods defined in the stereotype properties
}
var ins = new Sub();
console.log(ins.colors);
Copy the code

Passing parameters

One big advantage of borrowing constructor inheritance over prototype chain is that you can pass arguments to the superclass constructor in the subclass constructor

function B(name){
    this.name = name;
}
function A(){
    
    // Inherits B, passing arguments
    B.call(this.'MJJ');
    // Instance properties
    this.age = 28;
}
var p = new A();
alert(p.name);//'MJJ'
alert(p.age);/ / 28
Copy the code

Borrowing constructor problems

If you just borrow constructors, you can’t avoid the problem of the constructor pattern — methods are defined in constructors, so function reuse is out of the question. Also, methods defined in the parent class’s stereotype are not visible to subclasses. So it’s less used

Composite inheritance (critical)

Combinatorial inheritance refers to an inheritance pattern that combines a stereotype chain with a borrowed constructor technique to exploit the best of both. The idea behind this is to use stereotype chains to implement inheritance of public attributes and methods on stereotypes, while borrowing constructor inheritance to implement inheritance of private attributes on parent classes. In this way, function reuse is achieved by defining methods on the parent class prototype, while ensuring that each instance has the private attributes of the parent class.

function Super(name){
    this.name = name;
    this.colors = ['red'.'blue'.'green'];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    Super.call(this,name);
    this.age = age;
}
// Inheritance method
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    alert(this.age);
}
var ins = new Sub('mjj'.28);
ins.colors.push('black');
console.log(ins.colors);// ["red", "blue", "green", "black"]
ins.sayName();//'mjj'
ins.sayAge();/ / 28

var ins2 = new Sub('alex'.38);
console.log(ins2.colors);//["red", "blue", "green"]
ins2.sayName();//'alex'
ins2.sayAge();/ / 38
Copy the code

In the previous example, the Sub constructor defines two attributes: name and age. The Super prototype defines a sayName() method. The Super constructor is called in the Sub constructor with the name argument, followed by the definition of its own property age. We then assign an instance of Super to the prototype of Sub, and define the method sayAge() on that new prototype. In this way, different Sub instances can have their own attributes — including the colors attribute — and use the same method

Composite inheritance avoids the pitfalls of stereotype chains and borrowed constructors and combines their strengths, making it the most commonly used inheritance pattern in JavaScript.

Problems with composite inheritance

In any case, the superclass constructor is called twice: once when the subclass prototype is created and once inside the subclass constructor.

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 superclass constructor is called twice: once when the subclass stereotype is created and once inside the subclass 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 Super(name){
    this.name = name;
    this.colors = ['red'.'blue'.'green'];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    Super.call(this,name);
    this.age = age;
}
// Inheritance method
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    alert(this.age);
}
var ins = new Sub('mjj'.28);
ins.colors.push('black');
console.log(ins.colors);// ["red", "blue", "green", "black"]
ins.sayName();//'mjj'
ins.sayAge();/ / 28

var ins2 = new Sub('alex'.38);
console.log(ins2.colors);//["red", "blue", "green"]
ins2.sayName();//'alex'
ins2.sayAge();/ / 38
Copy the code

Parasitic combinatorial inheritance inherits properties by borrowing constructors and inherits methods through a hybrid form of prototype chains. The basic idea behind this is that you don’t need to call the constructor of the supertype to specify the stereotype of the subtype; all you need is a copy of the stereotype of the supertype. Essentially, you use parasitic inheritance to inherit the stereotype of the supertype and then assign the result to the stereotype of the subtype. The basic pattern of parasitic combinatorial inheritance is shown below.

function Super(name){
    this.name = name;
    this.colors = ['red'.'blue'.'green'];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    // Inherit instance attributes
    Super.call(this,name);
    this.age = age;
}
// Inherit public methods
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    alert(this.age);
}
var ins = new Sub('mjj'.28);
ins.colors.push('black');
console.log(ins.colors);// ["red", "blue", "green", "black"]
ins.sayName();//'mjj'
ins.sayAge();/ / 28

var ins2 = new Sub('alex'.38);
console.log(ins2.colors);//["red", "blue", "green"]
ins2.sayName();//'alex'
ins2.sayAge();/ / 38
Copy the code

Multiple inheritance

Multiple inheritance does not exist in JavaScript, which means that an object cannot inherit multiple objects at the same time, but workarounds can be used to do this.

<! DOCTYPEhtml>
<html lang="zh">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>28 Multiple Inheritance</title>
</head>
<body>
	<script type="text/javascript">
	// Multiple inheritance: an object inherits multiple objects simultaneously
	// Person Parent Me
	function Person(){
		this.name = 'Person';
	}
	Person.prototype.sayName = function(){
		console.log(this.name);
	}
	/ / customize the Parent
	function Parent(){
		this.age = 30;
	}
	Parent.prototype.sayAge = function(){
		console.log(this.age);
	}
	
	function Me(){
		// Inherit the attributes of Person
		Person.call(this);
		Parent.call(this);
	}
	// Inherit the Person method
	Me.prototype = Object.create(Person.prototype);
	// You cannot override a prototype object to implement inheritance from another object
	// Me.prototype = Object.create(Parent.prototype);
	// Object.assign(targetObj,copyObj)
	Object.assign(Me.prototype,Parent.prototype);
	
	// Specify the constructor
	Me.prototype.constructor = Me;
	var me = new Me();
	
	</script>
</body>
</html>
Copy the code

Object Related methods in an Object

Object Native methods of objects fall into two categories: methods of Object itself and methods of Object instances.

Methods are defined directly on the current constructor Object

/ / case
Object.xxx()
Copy the code

Object instance methods are the methods defined in Object.prototype. It can be shared directly by Object instances

Object.prototype.hello = function(){
    console.log('hello');
}
var obj = new Object(a); obj.hello();//hello
Copy the code

Static methods of Object

Static methods are methods specified in the Object itself

The following two methods are used to traverse the properties of an object

Object.keys()

Keys takes an Object and returns an array. The members of this array are all the property names of the object itself

var arr = ['a'.'b'.'c'];
Object.keys(arr);/ / [' 0 ', '1', '2');
var obj = {
    0:'a'.1:'b'.2: 'c'
}
Object.keys(obj);/ / [' 0 ', '1', '2')
Copy the code
Object.getOwnPropertyNames()

Object. The getOwnPropertyNames method and the Object. The keys are similar, is also accept an Object as a parameter, returns an array that contains all of the attributes of the Object itself.

var obj = {
    0:'a'.1:'b'.2: 'c'
}
Object.getOwnPropertyNames(obj);/ / / "0", "1", "2"]
Copy the code

For general Object, the Object. The keys () and Object getOwnPropertyNames () returns the result is the same. The only difference is when non-enumerable properties are involved. Only returns an enumerable Object. Keys method the properties of the Object. The getOwnPropertyNames method returns an enumeration of the property name.

var arr = ['a'.'b'.'c'];
Object.getOwnPropertyNames(arr);//['0','1','2','length'];
Copy the code

The above code, the array length attribute is an enumerated attribute, so only appear in the Object. The getOwnPropertyNames method returns the result.

Since JavaScript does not provide a way to count the number of attributes on an object, you can use these two methods instead.

var obj = {
    0:'a'.1:'b'.2: 'c'
}
Object.keys(obj).length / / 3
Object.getOwnPropertyNames(obj).length / / 3
Copy the code

In general, the Object. Keys method is the most popular way to traverse an Object’s properties.

Object.getPrototypeOf()

The object.getProtoTypeof method returns the prototype of the parameter Object. This is the standard way to get a prototype object

var Fn = function(){};
var f1 = new Fn();
console.log(Object.getPrototypeOf(f1) === Fn.prototyoe);//true
Copy the code

In the code above, the prototype for instance object F is F.prototype.

The following are prototypes for several special objects.

// The empty Object's prototype is object.prototype
Object.getPrototypeOf({}) === Object.prototype // true

// Object. Prototype is null
Object.getPrototypeOf(Object.prototype) === null // true

// The Function prototype is function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true
Copy the code
Object.setPrototypeOf()

The object. setPrototypeOf method takes two arguments, the first an existing Object and the second a prototype Object

var a = {};
var b = {x : 1};
Object.setPrototypeOf(a,b);
console.log(Object.getPrototypeOf(a));//{x:1}
a.x / / 1
Copy the code

The new command can be emulated using the object. setPrototypeOf method.

var F = function () {
  this.foo = 'bar';
};

//var f = new F();
/ / is equivalent to
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
Copy the code
Object.create()

A common way to generate instance objects is to make the constructor return an instance using the new command. But a lot of times, you can only get one instance object, it might not be generated by the constructor at all, so can you generate another instance object from one instance object?

JavaScript provides the object.create method to fulfill this requirement. The method takes an object as an argument and returns an instance object based on it. This instance fully inherits the properties of the prototype object.

// Prototype objects
var A = {
  print: function () {
    console.log('hello'); }};// Instance object
var B = Object.create(A);

Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true
Copy the code

In the code above, the object. create method generates an Object B from an Object A. B inherits all of A’s properties and methods.

Other methods

In addition to the two methods mentioned above,Object has a number of other static methods, which we’ll cover in more detail later

(1) Methods related to object attribute model

  • Object.getOwnPropertyDescriptor(): Gets the description object of an attribute.
  • Object.defineProperty(): Defines an attribute by describing an object.
  • Object.defineProperties(): Defines multiple properties by describing objects.

(2) Method of controlling object state (for details, refer to MDN by yourself)

  • Object.preventExtensions(): Prevents object expansion.
  • Object.isExtensible(): Determines whether an object is extensible.
  • Object.seal(): Disables object configuration.
  • Object.isSealed(): Determines whether an object is configurable.
  • Object.freeze(): Freezes an object.
  • Object.isFrozen(): Determines whether an object is frozen.

Object instance method

Methods are defined on object.prototype objects. We call them instance methods, and all instance objects of Object inherit these methods

Object Instance Object methods, there are six main.

  • Object.prototype.valueOf(): Returns the value of the current object.
  • Object.prototype.toString(): Returns the string representation of the current object.
  • Object.prototype.toLocaleString(): Returns the local string representation of the current object.
  • Object.prototype.hasOwnProperty(): Determines whether an attribute is a property of the current object itself or inherits from the prototype object.
  • Object.prototype.isPrototypeOf(): Determines whether the current object is a prototype of another object.
  • Object.prototype.propertyIsEnumerable(): Determines whether an attribute is enumerable.
Object.prototype.valueOf()

The valueOf method returns the valueOf an object, by default returning the object itself

var obj = new Object(a); obj.valueOf() === obj;//true
Copy the code

The main use of the valueof method is that it is called by default when JavaScript automatically converts to types

var obj = new Object(a);//JavaScript defaults to calling the valueOf() method to evaluate obj and add it to 1
console.log(1+obj);//"1[object Object]"
Copy the code

So, if you customize the valueOf method, you can get the result you want

var obj = new Object(a); obj.valueOf =function(){
    return 2;
}
console.log(1+ obj);/ / 3
Copy the code

ValueOf override Object.prototype.valueof with custom object.valueof

Object.prototype.toString()

The toString method returns a string representation of an object. By default, it returns a string of type

var obj1 = new Object(a);console.log(obj1.toString());//"[object Object]"
var obj2 = {a:1};
obj2.toString() // "[object Object]"
Copy the code

The result returned indicates the type of the object.

Object object by itself is not very useful, but by customizing the toString method, you can make an object get the desired string form when the automatic type conversion is performed.

var obj = new Object(a); obj.toString =function(){
    return 'hello';
}
console.log(obj + ' ' + 'world');//"hello world"
Copy the code

Like arrays, strings, functions, and the Date Object defines the custom toString method respectively, covering the Object. The prototype, the toString () method

[1.2.3].toString() / / "1, 2, 3"
'123'.toString() / / "123"

(function () {
  return 123;
}).toString()
// "function () {
// return 123;
// }"

(new Date()).toString()
// "Tue May 10 2016 09:11:31 GMT+0800 (CST)"
Copy the code

* * Object. The prototype. ToLocaleString * * method with the toString () method of usage.

Currently, there are three main objects that have custom toLocaleString methods

  • Array.prototype.toLocaleString()
  • Number.prototype.toLocaleString()
  • Date.prototype.toLocaleString()

For example, the toString and toLocaleString returns of date instances are different, and toLocaleString returns are dependent on the locale specified by the user

var date = new Date(a); date.toString()// "Tue Jan 01 2018 12:01:33 GMT+0800 (CST)"
date.toLocaleString() // "1/01/2018, 12:01:33 PM"
Copy the code
Object.prototype.isPrototypeOf()

The isPrototypeOf method of an instance object that determines whether the object is a prototype of the object.

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o2.isPrototypeOf(o3);//true
o1.isPrototypeOf(o3);//true
Copy the code

In the code above, o1 and O2 are both prototypes of O3. This indicates that the isPrototypeOf method returns true whenever the instance object is on the prototype chain of the parameter object.

Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
Copy the code

In the above code, because Object.prototype is at the top of the prototype chain, it returns true for all instances except for objects that inherit directly from NULL.

Object.prototype.__proto__

Instance object’s __proto__ property (two underscores before and two underscores behind), returns the prototype of that object. This property is read and write.

var obj = {};
var p = {};

obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true
Copy the code

The code above sets the p object to be the prototype of the obj object via the __proto__ attribute.

According to the language standard, the __proto__ attribute is deployed only by browsers, and other environments may not have it. The underlining indicates that it is essentially an internal property and should not be exposed to the user. Therefore, use this property as little as possible, and instead use Object.getProtoTypeof () and Object.setProtoTypeof () for reading and writing prototyped objects.

Object.prototype.hasOwnProperty

Object. The prototype. The hasOwnProperty method accepts a string as an argument and returns a Boolean value that indicates that the instance whether Object itself has the attribute.

var obj = {
    a: 123
}
obj.hasOwnProperty('b');//false
obj.hasOwnProperty('a');//true
obj.hasOwnProperty('toString');//false
Copy the code

In the code above, the object obj has an A property of its own, so returns true. The toString property is inherited, so it returns false.

Attribute description object

JavaScript provides an internal data structure that describes the properties of an object and controls its behavior, such as whether the property is writable, traversable, and so on. This internal data structure is called an attribute description object. Each attribute has its own attribute description object, which stores some meta information about the attribute

Here is an example of a property description object

{
  value: 123.writable: false.enumerable: true.configurable: false.get: undefined.set: undefined
}
Copy the code

The property description object provides six meta-properties

attribute meaning
value valueIs the property value of the property, which defaults toundefined.
writable writableIs a Boolean value that indicates whether the property value can be changed (that is, writable). The default istrue.
enumerable enumerableIs a Boolean value indicating whether the property is traversable, which is the defaulttrue. If set tofalseCan make certain operations (such asfor... inCirculation,Object.keys()Skip this property.
configurable configurableIs a Boolean value indicating configurability. Default istrue. If set tofalseWill prevent operations from overwriting the property, such as not being able to delete the property and not changing the property description object (valueAttributes excluded). In other words,configurableProperties control the writability of property description objects.
get getIs a function that represents the getter for the property, default toundefined.
set setIs a function that represents a setter for the property, which defaults toundefined.
Object.getOwnPropertyDescriptor()

Object. GetOwnPropertyDescriptor () method can obtain property description Object. Its first argument is the target object, and its second argument is a string corresponding to the name of an attribute of the target object.

var obj = {name:'MJJ'};
Object.getOwnPropertyDescriptor(obj,'name');
/*
{
configurable: true
enumerable: true
value: "MJJ"
writable: true
}
*/
//toString is an inherited property and cannot be retrieved
Object.getOwnPropertyDescriptor(obj,'toString');//undefined
Copy the code

Note: Object. GetOwnPropertyDescriptor () method can only be used for the properties of the Object itself, cannot be applied to inherit property

Object.defineProperty()

The Object.defineProperty method allows you to describe an Object by attribute, define or modify an attribute, and then return the modified Object.

The syntax is as follows:

Object.defineProperty(object, propertyName, attributesObject)
Copy the code

The object.defineProperty method takes three arguments, as follows.

  • Object: The object of the attribute
  • PropertyName: indicates the propertyName
  • AttributesObject: Indicates an attribute description object

For example, defining obj.name can be written as follows

var obj = Object.defineProperty({},'name', {value:'mjj'.writable:false.enumerable:true.configurable:false
})
console.log(obj.name);//mjj
obj.name = 'alex';
console.log(obj.name);//mjj
Copy the code

In the code above, the object.defineProperty () method defines the obj.p property. Because the writable property of the property description object is false, the obj.p property is not writable. Notice that the first argument to the object.defineProperty method here is {} (a newly created empty Object), the p property is defined directly on the empty Object and then returns the Object, which is a common use of Object.defineProperty().

If the attribute already exists, the object.defineProperty () method is equivalent to updating the attribute description Object for the attribute.

Object.defineProperties()

If multiple attributes are defined or modified at once, the object.defineProperties () method can be used

var obj = Object.defineProperties({}, {
  p1: { value: 123.enumerable: true },
  p2: { value: 'abc'.enumerable: true },
  p3: { 
    get: function () { 
        return this.p1 + this.p2 
    },
    enumerable:true.configurable:true}});console.log(obj.p1);/ / 123
console.log(obj.p2);//"abc"
console.log(obj.p3);//"123abc"
Copy the code

In the above code, Object.defineProperties() defines three properties of an obj Object simultaneously. Where, attribute P3 defines the value function get, that is, the value function will be called every time the property is read.

Note that once you define the get (or set) function, you cannot set the writable property to true or also define the value property, otherwise an error will be reported

var obj = {};

Object.defineProperty(obj, 'p', {
  value: 123.get: function() { return 456; }});// TypeError: Invalid property.
// A property cannot both have accessors and be writable or have a value

Object.defineProperty(obj, 'p', {
  writable: true.get: function() { return 456; }});// TypeError: Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute
Copy the code

The above code, defining both the GET and value attributes and setting the writable attribute to true, generates an error.

DefineProperty () and Object.defineProperties(), the default value of writable, cis, and Enumerable is false.

var obj = {};
Object.defineProperty(obj, 'foo'{});Object.getOwnPropertyDescriptor(obj, 'foo')
/*
{
configurable: false,
enumerable: false,
value: undefined,
writable: false,
}
*/
Copy the code

In the code above, we define obj.foo with an empty attribute description object, so we can see the default values for each meta-attribute.

Object.prototype.propertyIsEnumerable()

The propertyIsEnumerable() method of the instance object returns a Boolean value that determines whether a property can be iterated. Note that this method can only be used to determine the properties of the object itself and returns false for inherited properties.

var obj = {};
obj.p = 123;
obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false
Copy the code

In the code above, obj.p is traversable and obj.toString is an inherited property.

Yuan properties

value

The value attribute is the value of the target attribute

var obj = {};
obj.p = 123;

Object.getOwnPropertyDescriptor(obj, 'p').value / / 123
Object.defineProperty(obj, 'p', { value: 246 });
obj.p / / 246
Copy the code

The above code is an example of reading or overwriting obj.p via the value property.

writable

The writable attribute is a Boolean value that determines whether the value of the target attribute can be changed.

var obj = {};

Object.defineProperty(obj, 'a', {
  value: 37.writable: false
});
obj.a / / 37
obj.a = 25;
obj.a / / 37
Copy the code

In the code above, the writable property of obj. A is false. Then, changing the value of obj. A won’t do anything.

Note that in normal mode, assigning to a property with writable false will not report an error, but will silently fail. However, an error is reported in strict mode, even if the a attribute is reassigned to the same value.

'use strict';
var obj = {};

Object.defineProperty(obj, 'a', {
  value: 37.writable: false
});

obj.a = 37;
// Uncaught TypeError: Cannot assign to read only property 'a' of object
Copy the code

The above code is in strict mode and will report an error for any assignment to obj.

If the writable of an attribute of the prototype object is false, the child object will not be able to customize that attribute.

var proto = Object.defineProperty({}, 'foo', {
  value: 'a'.writable: false
});

var obj = Object.create(proto);
obj.foo = 'b';
obj.foo; // 'a'
Copy the code

In the code above, proto is a prototype object whose foo property is unwritable. Obj objects inherit from Proto and can no longer customize this property. In strict mode, doing so also throws an error.

One way around this limitation, however, is to override the attribute description object. The reason is that in this case, the prototype chain is completely ignored.

var proto = Object.defineProperty({}, 'foo', {
  value: 'a'.writable: false
});

var obj = Object.create(proto);
Object.defineProperty(obj, 'foo', {
  value: 'b'
});

obj.foo // "b"
Copy the code
enumerable

Enumerable Returns a Boolean value that indicates whether the target property is traversable.

If an attribute’s Enumerable is false, the following three operations do not retrieve it

  • for... incycle
  • Object.keymethods
  • JSON.stringifymethods

Therefore, Enumerable can be used to set a “secret” property.

var obj = {};

Object.defineProperty(obj, 'x', {
  value: 123.enumerable: false
});

obj.x / / 123

for (var key in obj) {
  console.log(key);
}
// undefined
Object.keys(obj)  / / []
JSON.stringify(obj) / / "{}"
Copy the code

In the code above, the obj.x property’s Enumerable is false, so it can’t be retrieved by normal traversal operations, making it a bit of a “secret” property, but not really private. You can still retrieve its value directly.

Note that for… The in loop includes inherited properties; the object. keys method does not. If you need to get all the attributes of the Object itself, whether can traverse, you can use the Object. The getOwnPropertyNames method.

In addition, the json.stringify method rules out enumerable properties that are false, which can sometimes be leveraged. You can set Enumerable to False if you want to exclude some properties in the JSON output of your object.

configurable

The property description object returns a Boolean value, which determines whether it can be modified without any additional information. If the 64x is false, the value, writable, Enumerable, and cis cannot be modified anymore.

var obj = Object.defineProperty({}, 'p', {
  value: 1.writable: false.enumerable: false.configurable: false
});

Object.defineProperty(obj, 'p', {value: 2})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {writable: true})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {enumerable: true})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {configurable: true})
// TypeError: Cannot redefine property: p
Copy the code

In the above code, the 64x of OBJ. P is false. If any changes are made to the value, writable, Enumerable, and 64x, any error is reported.

Note that writable will only report an error if false is changed to true, which is allowed.

var obj = Object.defineProperty({}, 'p', {
  writable: true.configurable: false
});

Object.defineProperty(obj, 'p', {writable: false})
// The modification succeeded
Copy the code

As for the value, any changes are allowed if either writable or 64x is true.

var o1 = Object.defineProperty({}, 'p', {
 value: 1.writable: true.configurable: false
});

Object.defineProperty(o1, 'p', {value: 2})
// The modification succeeded

var o2 = Object.defineProperty({}, 'p', {
 value: 1.writable: false.configurable: true
});

Object.defineProperty(o2, 'p', {value: 2})
// The modification succeeded
Copy the code

In addition, if writable is false, assignment to the target property is performed without error, but will not succeed.

var obj = Object.defineProperty({}, 'p', {
  value: 1.writable: false.configurable: false
});

obj.p = 2;
obj.p / / 1
Copy the code

In the above code, the writable of obj.p is false. Assigning to obj.p directly will not work. If it is in strict mode, an error is reported.

Configurability determines whether a target attribute can be deleted (delete).

var obj = Object.defineProperties({}, {
  p1: { value: 1.configurable: true },
  p2: { value: 2.configurable: false}});delete obj.p1 // true
delete obj.p2 // false

obj.p1 // undefined
obj.p2 / / 2
Copy the code

In the above code, OBJ. P1 is configured with true, so it can be deleted without any additional information

accessor

In addition to being defined directly, attributes can also be defined using accessors. The store function, called setter, uses properties to describe the set property of the object. The value function, called a getter, uses properties to describe the get property of an object

Once an accessor is defined for a target property, the corresponding function is executed whenever it is accessed. With this feature, you can implement many advanced features, such as a property that disables assignment

var obj = Object.defineProperty({},'p', {get:function(){
        return 'getter';
    },
    set:function(value){
        console.log('setter:'+value);
    }
})
obj.p //"getter"
obj.p = 123;//"setter:123"
Copy the code

In the code above, obj.p defines the get and set attributes. When obj.p is evaluated, get is called; When you assign, set is called.

JavaScript also provides another way to write accessors.

var obj = {
    get p() {return 'getter';
    },
    set p(value) {console.log('setter:'+ value); }}Copy the code

The above notation is equivalent to defining an attribute description object, and is more widely used.

Note that the get function cannot take arguments, and the set function can take only one argument (that is, the value of an attribute).

Accessors are used when the value of a property depends on the internal data of the object

var obj = {
    $n : 5.get next() {return this.$n++;
    },
    set next(value) {if(value >= this.$n){
            this.$n = value;
        }else{
            throw new Error('New value must be greater than current value'); }}}; obj.next/ / 5
obj.next = 10;
obj.next / / 10
obj.next = 5;
// Uncaught Error: The new value must be greater than the current value
Copy the code

In the code above, both the store and the value functions of the next attribute must depend on the internal attribute $n

Depth copy

Copy of basic type

Let’s start with a very classic piece of code

var a = 1;
var b = a;
a = 200;
console.log(a);/ / 200
console.log(b);/ / 1
Copy the code

The base type is passed by value, the reference type is passed by reference, and the value as the base type is stored in stack memory and can be used directly. Assignment is what it is, and is not affected by changes in the passed element.

A copy of the reference type

To put it simply, a reference type generates a pointer and stores it in heap memory. When we assign a value to a reference type, we write in stack memory, which means we get a pointer. This pointer is the code that points to the reference type in stack memory.

There are two types of copy involved: deep copy and shallow copy

The operation of the copied data does not affect the value copy of the original data, which is a deep copy. In any case, it is a shallow copyCopy the code
Shallow copy
var a = {
    name:'mjj'.age:20.hobby:'eat'.friend: {name:'alex'.age:38}}function shadowCopy(to,from){
    for(var key in from){
        to[key] = from[key]
    }
    return to;
}
var newObj = shadowCopy({},a);
newObj.age = 18;
newObj.friend.name = 'huang';
/* {age: 20,// not changed friend: {name: "yellow ",// not changed, so that the same reference age: 38}, hobby: "eat" name:" MJJ} */

Copy the code

We find that, first of all, the shallow copy does not assign a value directly. The shallow copy creates a new object and copies all the properties of the source object, copying the values instead of the references.

As we know, objects are accessed by address reference. A shallow copy copy only copies the attributes of the first layer and does not recursively copy all the values. Therefore, the operation of copying data affects the original data, so it is a shallow copy.

Deep copy

A deep copy is a full copy of the target, not just a layer of references, but also the values, as in a shallow copy.

As long as the deep copy, they will never communicate, no one will influence each other.

With deep copy, newly created objects can be completely separated from the original

var a = {
    name:'mjj'.age:20.hobby:'eat'.friend: {name:'alex'.age: 38.hobby:'chicken'}}function deepCopy(to,from){
    for(var key in from) {// Do not iterate over properties on the prototype chain
        if(from.hasOwnProperty(key)){
            // If the value is an object and has a value, recurse
            if(from[key] && typeof from[key] === 'object') {// Distinguish between a generic object and an array object
               to[key] = from[key].constructor === Array? [] : {}; to[key] = deepCopy(to[key],from[key]);
            }else{
                // If not, assign directly
                to[key] = from[key]; }}}return to;
  
}
var newObj = deepCopy(a);
newObj.age = 18;
newObj.friend.name = 'huang';
Copy the code

The hasOwnProperty property is used to filter out inherited properties.