Although the Object constructor or literal of an Object can be used to create a single Object, these methods have a significant disadvantage. Creating objects of the same structure creates a lot of duplicate code.

const person1 = {
    name: 'Zhang san'.age: 18.job: 'Engineer'.sayName: function() {
        alert(this.name); }};const person2 = {
    name: 'Li si'.age: 18.job: 'Engineer'.sayName: function() {
        alert(this.name); }};Copy the code

Person1 and person2 have the same attributes and methods, but there is no reuse between them. To solve this problem, a variant of the factory model is being used.

The factory pattern

The factory pattern abstracts the process of creating concrete objects. Because there are no classes in JavaScript (classes in ES6 are also functions), developers invent a function that encapsulates the details of creating an object with a particular interface, as shown in the following example:

function createPerson(name, age, job) {
    let o = new Object(a); o.name = name; o.age = age; o.job = job; o.sayName =function() {
        console.log(this.name);
    }
    return o;
}

const person1 = createPerson('Zhang san'.18.'Engineer');
const person2 = createPerson('Li si'.18.'Doctor');
Copy the code

The createPerson() function builds a Person object with all the necessary information from the parameters it accepts. This function can be called countless times, each time returning a new Person object.

However, while the factory pattern solves the problem of creating multiple similar objects, it does not solve the object identification problem, that is, the inability to know the type of an object.

As JavaScript evolved, a new pattern emerged.

Constructor pattern

Constructors in ECMAScript can be used to create objects of a specific type. Native constructors, such as Object and Array, are automatically called at run time in the execution environment.

Therefore, we can also design constructors for custom objects. Use constructors to rewrite the previous example.

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name); }}const person1 = new Person('Zhang san'.18.'Engineer');
const person2 = new Person('Li si'.18.'Doctor');
Copy the code

The Person() function replaces the createPerson() function. And there are several differences in their code:

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

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

  1. Create a new object;
  2. Assign the constructor’s scope to the new object (pointing to this);
  3. Execute the code in the constructor;
  4. Returns a new object.

When an object is created using the Person constructor, it is added with a constructor property that points to the Person, which is the constructor’s pointer address.

console.log(person1.constructor === Person); // true
console.log(person2.constructor === Person); // true
Copy the code

The constructor property of an object can be used to identify the object’s type, an essential feature for using JavaScript for object-oriented programming.

However, when detecting types, it is more reliable to use the instanceof operator because the constructor property may be modified from time to time.

Let’s verify:

console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
Copy the code

If you test whether the object created by createPerson() is an instance of Person, false will be returned.

1. The difference between constructors and ordinary functions

The only difference between constructors and normal functions is the way they are called. However, constructors are functions, and there is no special syntax for defining constructors.

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

For example, the Person() function defined in the previous example can be called in any of the following ways.

// as a constructor
const person = new Person('Zhang san'.18.'Engineer');
person.sayName(); // Zhang san

// called as a normal function
Person('Li si'.18.'Doctor');
global.sayName(); // Li si
Copy the code

When using the new operator to create a new object, Person() is used as the constructor. Instead of calling directly with the new operator, properties and methods are added to the global object.

When a function is called in the global scope, the this object always points to the Global object (the window object in browsers). Therefore, after calling the function, the sayName() method can be called from the window/global object and return the correct value.

2. Constructor problems

Constructors, while useful, have their drawbacks. The main problem with using constructors is that each method has to be recreated on each instance.

Let’s look at the definition of the Person constructor:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    **this.sayName = new Function('console.log(this.name)'); * *}const person1 = new Person('Zhang san'.18.'Engineer');
const person2 = new Person('Li si'.18.'Doctor');
Copy the code

Person1 and person2 both have a method named sayName() created with this Function, but the two methods are not the same Function instance.

Creating functions in this way leads to different scope chains and identifier resolution that do the same thing, but the different instances are not shared.

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

Creating two instances of Function that do the same thing is a waste of memory. With this, we don’t need to bind the function to a specific object when we construct it. Therefore, the problem can be solved by moving the function definition outside the constructor, as follows.

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

function sayName() {
    console.log(this.name);
}
Copy the code

We move the definition of the sayName() function outside the constructor. Inside the constructor, we set the sayName property of Person to be equal to the global sayName function. Thus, since sayName contains a pointer to a function, person1 and person2 objects share the same sayName() function defined in the global scope.

This does solve the problem of two functions doing the same thing, but introduces two new problems:

  1. Functions defined in a global scope can really only be called by an object, making the global scope a misnomer.
  2. If the object needs to define many methods, then we need to define many global functions, so our custom object type has no encapsulation.

The introduction of the prototype pattern solves this problem nicely.

The prototype pattern

Each function we create actually has a Prototype property, which is a pointer to an object whose properties and methods are shared by all instances created by the function. Prototype objects are called prototype objects for these instances.

This allows us to add the methods that define objects in the constructor directly to the prototype object, as shown in the following example.

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

Person.prototype.sayName = function() {
    console.log(this.name);
}
Copy the code

We add the sayName() method and all the properties directly to the Person prototype property, so that all instances of Person share the same method while ensuring that it only works in the Person scope.

summary

Contents involved in this paper:

  • Check the type of an object;
  • How to define functions (not calls) using the new keyword;
  • How to create custom constructors correctly.