zishu's blog

zishu's blog

一个热爱生活的博主。https://zishu.me

Object-Oriented Programming

Object-oriented programming decomposes the transaction that constitutes a problem into various objects. The purpose of establishing objects is not to complete steps one by one, but to describe the behavior that occurs in the process of solving the entire problem, aiming to write generic code, enhance code reuse, and shield differences.

1. What is Object-Oriented Programming#

JavaScript is prototype-based, based on object-oriented programming.

Object-oriented programming combines data and methods that operate on that data into a single entity—an object. It abstracts the commonalities of similar objects to form classes.

image

1. Procedural Programming#

A project (or an event) is completed step by step in order from beginning to end, determining what to do first and what to do next until completion, which is also how we humans approach tasks.

From top to bottom, first determine an overall framework, then add bricks and tiles, gradually achieving the desired effect. This approach is suitable for simple systems and is easy to understand. However, it is difficult to cope with complex systems, not easy to maintain and extend, and hard to reuse.

Procedural programming analyzes the steps to solve a problem and then implements these steps one by one using functions, which can be called sequentially when needed. It emphasizes the actions taken to complete the task, aligning closely with our daily problem-solving thinking.

2. Object-Oriented Programming#

A project (or an event) is divided into smaller projects, with each part responsible for a specific function, ultimately forming a whole. Components are designed first, and then assembled, which is suitable for large and complex systems.

Object-oriented programming decomposes the transaction that constitutes a problem into various objects. The purpose of establishing objects is not to complete steps one by one, but to describe the behavior that occurs in the process of solving the entire problem, aiming to write generic code, enhance code reuse, and shield differences.

To understand object-oriented programming, one must first grasp the concepts of classes and objects.

“What are Classes and Objects?”

2. Methods to Create Objects#

1. Creating Literals and Instances#

window.onload = function() {
    // Instance
    var person = new Object();
    person.name = 'Xiao Ming';
    person.age = 22;
    person.year = function() {
        console.log(this.name + ' is ' + this.age + ' years old this year!')
    };
    person.year();

    // Literal
    var student = {
        name: 'Xiao Ming',
        age: 22,
        year: function () {
            console.log(this.name + ' is ' + this.age + ' years old this year!')
        }
    }
    student.year();
}

// Xiao Ming is 22 years old this year!

Both outputs are the same, with the console output:
image

Disadvantage: Repeated instantiation of objects, high code redundancy.

2. Factory Pattern#

window.onload = function() {
    function createObj(name, age) {
        var obj = new Object();
        obj.name = name,
        obj.age = age,
        obj.year = function() {
            console.log(this.name + ' is ' + this.age + ' years old this year!')
        }
        return obj;
    }
    var obj = createObj('Xiao Ming', 22);
    obj.year();
}

// Xiao Ming is 22 years old this year!

Advantage: Solves the problem of repeated instantiation of objects.
Disadvantage: Cannot identify the type of the object, as all instances point to one prototype.

3. Constructor Function#

window.onload = function() {
    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.year = function() {
            console.log(this.name + ' is ' + this.age + ' years old this year!')
        }
    }
    var student = new Person('Xiao Ming', 22);
    student.year();
}

// Xiao Ming is 22 years old this year!

Advantage: Can identify the type of the object.
Disadvantage: Multiple instances create duplicate methods, which cannot be shared.

4. Prototype Pattern#

window.onload = function() {
    function Par() {}
    Par.prototype = {
        constructor: 'Par',
        name: 'Xiao Ming',
        age: 22,
        year: function() {
            console.log(this.name + ' is ' + this.age + ' years old this year!')
        }
    };
    var son = new Par();
    son.year();
}

// Xiao Ming is 22 years old this year!

Disadvantage: All instances share its properties and methods, cannot pass parameters and initialize property values.

This is a mixed writing style of constructor functions and prototype patterns, possessing their respective advantages. The constructor function shares instance properties, while the prototype pattern shares methods and desired properties, allowing for parameter passing and property value initialization.

First, define the object's properties and methods using the constructor function, then create methods using the prototype pattern. The properties used are accessed through prototype, and there is a constructor property that can point to the function object (constructor) being operated on.

For example, constructor: Par indicates that the following prototype method points to the Par() object (constructor).

window.onload = function() {
    function Par(name, age) {
        this.name = name;
        this.age = age;
    }
    Par.prototype = {
        constructor: Par,
        year: function() {
            console.log(this.name + ' is ' + this.age + ' years old this year!');
        }
    };
    var son = new Par('Xiao Ming', 22)
    son.year();
}

// Xiao Ming is 22 years old this year!

3. Prototype and Prototype Chain#

1. Prototype Object#

  1. Function objects have a prototype property that points to the function's prototype object (an object created in the browser's memory). Prototype objects have a constructor property that points to the function object (constructor) where the prototype property resides.
window.onload = function() {
    function Par(name, age) {
        this.name = name;
        this.age = age;
    }
    Par.prototype = {
    // constructor points to the object
        constructor: Par,
        year: function() {
            console.log(this.name + ' is ' + this.age + ' years old this year!');
        }
    };
    var son = new Par('Xiao Ming', 22)
    son.year();

/*********************************************/
    console.log(Par.prototype)
    console.log(Par.prototype.constructor)
/*********************************************/
}

From the console, we can see that

The prototype property of the constructor points to the prototype object.

The constructor property of the prototype object points to the constructor.

image

  1. When a constructor function creates an instance, that instance will have a hidden property __proto__, which points to the constructor's prototype object.
console.log(son.__proto__ === Par.prototype)

// true
  1. All constructor function prototypes are of object type.
console.log(typeof Par.prototype)

// object
  1. The prototype of Function is an empty function, and the __proto__ property of all built-in functions points to this empty function.
console.log(Math.__proto__)

image

  1. If both the constructor instance and the prototype object define a property with the same name, calling it will shadow the property in the prototype object. To access the value of the property in the prototype object, the same-named property in the instance (constructor) must be completely deleted using the delete method.
window.onload = function () {
    function Par(name) {
        this.name = name;
    }
    Par.prototype.name = "Zhang San";
    var son = new Par("Li Si");
    console.log(son.name); // Li Si
    console.log(son.__proto__.name); // Zhang San

    // Use delete to remove the same-named property value in the instance
    console.log(delete son.name);   // true
    console.log(son.name); // Zhang San
}
  1. The hasOwnProperty(propertyName) method can be used to determine whether a property exists in the constructor or in the prototype object.

true indicates it exists in the constructor; false indicates it exists in the prototype object.

console.log(Par.hasOwnProperty(name));  // false
  1. The in operator can determine whether a property exists (it can exist in either the constructor or the prototype object).
window.onload = function () {
    function Par(name, age) {
        this.name = name;
        this.age = age;
    }
    Par.prototype = {
        constructor: Par,
        year: function() {
            console.log(this.name + this.age)
        }
    };
    var son = new Par('xm', '22')
    son.year();
    console.log('name' in Par); // true
    console.log('age' in Par);  // false
}

The same two properties, checking whether they exist in the instance or prototype object yields different results.

Reference: “Is there a property in the object?” https://www.cnblogs.com/IwishIcould/p/12333739.html

2. Differences Between proto and prototype#

  1. The prototype property exists only on function objects, while the __proto__ property exists on all objects.

  2. prototype is pointed to by the function object to the prototype object, while __proto__ is pointed to by the instance to the function object's prototype object.

  3. The prototype chain allows instances of the parent type to serve as the prototype object for the child type, creating a chain relationship known as the prototype chain.

image

3. Inheritance#

  1. Prototype Chain Inheritance

Advantage: Properties and methods defined in the parent class prototype can be reused.
Disadvantage: Instances of the child class do not have their own properties and cannot pass parameters to the parent class.

function test1() {
    function SuperType() {
        this.city = [ "Beijing", "Shanghai", "Tianjin" ];
        this.property = true;
    }
    SuperType.prototype = {
        constructor : SuperType,     // Maintain the integrity of the constructor and prototype object
        age : 15,
        getSuperValue : function() {
            return this.property;
        }
    };
    function SonType() {
        this.property = false;
    }

    // Rewrite the child class prototype to point to the parent class instance: inherit the parent class prototype
    SubType.prototype = new SuperType();

    SubType.prototype = {
        constructor : SubType,
        getSonType : function() {
            return this.property;
        }
    };

    // Verify advantages
    let son = new SubType();
    console.log(son.age); // 15
    console.log(son.getSuperValue()); // false

    // Verify disadvantages
    let instance1 = new SubType();
    instance1.city.push("Chongqing");
    console.log(instance1.city); // ["Beijing", "Shanghai", "Tianjin", "Chongqing"]

    let instance2 = new SubType();
    console.log(instance2.city); // ["Beijing", "Shanghai", "Tianjin", "Chongqing"]

}

// test1();
  1. Constructor Inheritance

Advantage: Instances of the child class have their own properties and can pass parameters to the parent class, solving the disadvantages of prototype chain inheritance.
Disadvantage: Properties and methods of the parent class prototype cannot be reused.

function test2() {
    function SuperType(name) {
        this.name = name;
        this.city = [ "Beijing", "Shanghai", "Tianjin" ]
    }
    SuperType.prototype = {
        constructor : SuperType,
        age : 18,
        showInfo : function() {
            return this.name;
        }
    };

    function SubType() {
        // Call the parent class using call() or apply() method to share the same this, inheriting the child class instance properties.
        SuperType.call(this, "Zhang San");
    }

    // Verify advantages
    let instance = new SubType();
    instance.city.push("Chongqing");
    console.log(instance.city); // ["Beijing", "Shanghai", "Tianjin", "Chongqing"]

    let instance1 = new SubType();
    console.log(instance1.city); // ["Beijing", "Shanghai", "Tianjin"]

    // Verify disadvantages
    console.log(instance.age); // undefined
    instance.showInfo(); // son.showInfo is not a function
}

// test2();
  1. Combination Inheritance (Recommended)

Advantage: Properties and methods of the prototype can be reused, and each instance of the child class has its own properties.
Disadvantage: The parent class constructor is called twice, and the parent class instance properties in the child class prototype are overwritten by the child class instances.

function test3() {
    function SuperType(name) {
        this.name = name;
        this.city = [ "Beijing", "Shanghai", "Tianjin" ]
    }
    SuperType.prototype = {
        constructor : SuperType,
        showInfo : function() {
            console.log(this.name + " is " + this.age + " years old");
        }
    };

    function SubType(name, age) {
        // 1. Inherit instance properties through the constructor method.
        SuperType.call(this, name);
        this.age = age;
    }

    // 2. Inherit prototype methods through the prototype chain.
    SubType.prototype = new SuperType();

    // Verify advantages
    let instance = new SubType("Zhang San", 15);
    instance.showInfo(); // Zhang San is 15 years old

    let instance1 = new SubType();
    instance1.city.push("Chongqing");
    console.log(instance1.city); // ["Beijing", "Shanghai", "Tianjin", "Chongqing"]

    let instance2 = new SubType();
    console.log(instance2.city); // ["Beijing", "Shanghai", "Tianjin"]

}

// test3();
  1. Parasitic Combination Inheritance (Recommended)

Advantage: Solves the disadvantages of combination inheritance, high efficiency.
Disadvantage: Basically none.

function test4() {
    function inheritPrototype(subType, superType) {
        // 1. Inherit the parent's prototype.
        var prototype = Object.create(superType.prototype);
        // 2. Rewrite the polluted constructor.
        prototype.constructor = subType;
        // 3. Rewrite the child's prototype.
        subType.prototype = prototype;
    }
    function SuperType(name) {
        this.name = name;
        this.city = [ "Beijing", "Shanghai", "Tianjin" ];
    }

    SuperType.prototype.sayName = function() {
        console.log(this.name);
    };

    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }

    // Point the parent class prototype to the child class.
    inheritPrototype(SubType, SuperType);

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

    // Verify advantages
    let instance = new SubType("Zhang San", 15);
    instance.sayName(); // Zhang San

    let instance1 = new SubType();
    instance1.city.push("Chongqing");
    console.log(instance1.city); // ["Beijing", "Shanghai", "Tianjin", "Chongqing"]

    let instance2 = new SubType();
    console.log(instance2.city); // ["Beijing", "Shanghai", "Tianjin"]
}

// test4();

4. New ES6 Method - class#

The new keyword class was introduced to JavaScript in ES6 to simplify class definitions.

Using function methods:

function Person(name) {
    this.name = name;
}

Person.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

var son = new Person('xm')
son.hello();    // Hello, xm!

Using class to implement:

class Person {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log('Hello, ' + this.name + '!');
    }
}

var son = new Person('xm')
son.hello();    // Hello, xm!

As you can see, when defining class, the constructor constructor property and the function hello() method on the prototype object are directly included, eliminating the need for the function keyword.

It is important to note that in the original writing, the constructor and prototype object were written separately, but now with class, both can be combined into one object, with only the parameter passing and method calling remaining the same.

Class Inheritance

Another significant advantage of using class to define objects is that inheritance becomes more convenient. Consider the amount of code we need to write to derive a PrimaryPerson from Person. Now, we no longer need to worry about intermediate objects for prototype inheritance, prototype object constructors, etc., and can directly implement it through extends:

class PrimaryPerson extends Person {
    constructor(name, grade) {
        super(name); // Remember to call the parent class's constructor!
        this.grade = grade;
    }

    myGrade() {
        alert('I am in grade ' + this.grade);
    }
}

Note that the definition of PrimaryPerson is also implemented using the class keyword, and extends indicates that the prototype chain object comes from Person. The constructor of the subclass may differ from that of the parent class.

For example, PrimaryPerson requires both name and grade parameters, and it needs to call the parent class's constructor using super(name); otherwise, the parent class's name property will not be initialized correctly.

PrimaryPerson automatically inherits the hello method from the parent class Person, and we also define a new method myGrade in the subclass.

What is the difference between the class introduced in ES6 and the original JavaScript prototype inheritance?

In fact, there is no difference; the purpose of class is to allow the JavaScript engine to implement the prototype chain code that we originally had to write ourselves. In short, the benefit of using class is that it greatly simplifies prototype chain code.

However!

Currently, not all browsers support class, so caution is advised when choosing to use it!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.