zishu's blog

zishu's blog

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

Object-Oriented Programming

Object-oriented programming breaks down the transactions that make up a problem into various objects. The purpose of creating objects is not to complete steps one by one, but to describe the behavior that occurs during 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 the 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, 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 establish an overall framework, then gradually build upon it to achieve the desired effect. This approach is suitable for simple systems and is easy to understand. However, it struggles with complex systems, is difficult to maintain and extend, and is 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 a task, aligning closely with our everyday 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, making it suitable for large and complex systems.

Object-oriented programming breaks down the transactions that make up a problem into various objects. The purpose of creating objects is not to complete steps one by one, but to describe the behavior that occurs during 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 displaying:
image

Disadvantage: Redundant object instantiation, 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 redundant object instantiation.
Disadvantage: Cannot identify the type of the object, as all instances point to the same 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 or 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 function points to the prototype object.

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

image

  1. When a constructor function creates an instance, that instance will have a hidden property __proto__, which points to the constructor function'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 function instance and the prototype object define a property with the same name, the instance's property will shadow the prototype object's property. To access the prototype object's property value, the same-named property must be completely deleted from the instance 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 instance's same-named property value
    console.log(delete son.name);   // true
    console.log(son.name); // Zhang San
}
  1. Using hasOwnProperty(propertyName) can determine whether a property exists in the constructor function or in the prototype object.

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

console.log(Par.hasOwnProperty(name));  // false
  1. The in operator can check whether a property exists (it can exist in both the constructor function and 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: “Does the Object Have a Certain Property?” 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 function objects to the prototype object, while __proto__ is pointed to by instances to the function object's prototype object.

  3. The prototype chain allows instances of a parent type to serve as the prototype object of a 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: Child class instances 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's prototype to point to the parent's instance: inherit the parent's 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: Child class instances 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() {
        // The parent class calls the call() or apply() method, sharing the same this with the child class, achieving inheritance of 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 child class instance 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, with 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, and its purpose is 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 function constructor property and the function hello() method on the prototype object are included directly, eliminating the need for the function keyword.

It is important to note that in the original syntax, the constructor function and the prototype object were written separately, but now with class, both can be combined into a single object, with only the parameter passing and method calling syntax 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.; we can directly implement it using 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 achieved using the class keyword, and extends indicates that the prototype chain object comes from Person. The child class's constructor may differ from that of the parent class.

For example, PrimaryPerson requires both name and grade parameters and 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 previously had to write ourselves. In short, the benefit of using class is that it greatly simplifies the prototype chain code.

However!

Currently, not all browsers support class, so caution is advised when making a choice!

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