Chapter 2: Object-Oriented
Chapter 2: Object-Oriented
1. Class
To be object-oriented and manipulate objects, you must first have objects. The next question is how to create objects. To create an object, you must first define a class. A class can be understood as a model of an object. In the program, you can create specified types of objects based on the class. For example, you can create a person's object through the Person class, a dog's object through the Dog class, and a car's object through the Car class. Different classes can be used to create different objects.
Define a class:
class ClassName { PropertyName: Type; constructor(Parameter: Type){ this.PropertyName = Parameter; } MethodName(){ .... } }
Example:
class Person{ name: string; age: number; constructor(name: string, age: number){ this.name = name; this.age = age; } sayHello(){ console.log(`Hello, I am ${this.name}`); } }
Using the class:
const p = new Person('Sun Wukong', 18); p.sayHello();
2. Characteristics of Object-Oriented
Encapsulation
An object is essentially a container for properties and methods. Its main function is to store properties and methods, which is called encapsulation.
By default, the properties of an object can be modified arbitrarily. To ensure data security, you can set the permissions of properties in TS.
Read-only properties (readonly):
- If you add a readonly when declaring a property, the property becomes a read-only property and cannot be modified.
In TS, properties have three modifiers:
- public (default), can be modified in classes, subclasses, and objects
- protected, can be modified in classes and subclasses
- private, can be modified in the class
Example:
public
class Person{ public name: string; // Writing or not writing is public public age: number; constructor(name: string, age: number){ this.name = name; // Can be modified in the class this.age = age; } sayHello(){ console.log(`Hello, I am ${this.name}`); } } class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; // Can be modified in the subclass } } const p = new Person('Sun Wukong', 18); p.name = 'Zhu Bajie'; // Can be modified through the object
protected
class Person{ protected name: string; protected age: number; constructor(name: string, age: number){ this.name = name; // Can be modified this.age = age; } sayHello(){ console.log(`Hello, I am ${this.name}`); } } class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; // Can be modified in the subclass } } const p = new Person('Sun Wukong', 18); p.name = 'Zhu Bajie'; // Cannot be modified
private
class Person{ private name: string; private age: number; constructor(name: string, age: number){ this.name = name; // Can be modified this.age = age; } sayHello(){ console.log(`Hello, I am ${this.name}`); } } class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; // Cannot be modified in the subclass } } const p = new Person('Sun Wukong', 18); p.name = 'Zhu Bajie'; // Cannot be modified
Property Accessors
For some properties that you do not want to be modified arbitrarily, you can set them as private.
Directly setting them as private will prevent modification of the properties through the object.
You can define a set of methods for reading and setting properties in the class. This method of reading or setting properties is called property accessors.
The method for reading properties is called a setter method, and the method for setting properties is called a getter method.
Example:
class Person{ private _name: string; constructor(name: string){ this._name = name; } get name(){ return this._name; } set name(name: string){ this._name = name; } } const p1 = new Person('Sun Wukong'); console.log(p1.name); // Read the name property through getter p1.name = 'Zhu Bajie'; // Modify the name property through setter
Static Properties
Static properties (methods), also known as class properties. Static properties can be used directly through the class without creating an instance.
Static properties (methods) start with static.
Example:
class Tools{ static PI = 3.1415926; static sum(num1: number, num2: number){ return num1 + num2 } } console.log(Tools.PI); console.log(Tools.sum(123, 456));
this
- In a class, using this refers to the current object.
Inheritance
Inheritance is another characteristic of object-oriented programming.
Through inheritance, you can bring in properties and methods from other classes into the current class.
Example:
class Animal{ name: string; age: number; constructor(name: string, age: number){ this.name = name; this.age = age; } } class Dog extends Animal{ bark(){ console.log(`${this.name} is barking!`); } } const dog = new Dog('Wangcai', 4); dog.bark();
Inheritance allows you to extend a class without modifying it.
Overriding
When inheritance occurs, if a method in the subclass replaces a method with the same name in the parent class, this is called method overriding.
Example:
class Animal{ name: string; age: number; constructor(name: string, age: number){ this.name = name; this.age = age; } run(){ console.log(`The run method in the parent class!`); } } class Dog extends Animal{ bark(){ console.log(`${this.name} is barking!`); } run(){ console.log(`The run method in the subclass overrides the run method in the parent class!`); } } const dog = new Dog('Wangcai', 4); dog.bark();
In the subclass, you can use super to reference the parent class.
Abstract Class (abstract class)
An abstract class is a class specifically designed to be inherited by other classes. It can only be inherited by other classes and cannot be instantiated.
abstract class Animal{ abstract run(): void; bark(){ console.log('The animal is barking~'); } } class Dog extends Animal{ run(){ console.log('The dog is running~'); } }
Methods that start with abstract are called abstract methods. Abstract methods do not have a method body and can only be defined in an abstract class. When inheriting an abstract class, the abstract methods must be implemented.
3. Interface
The role of an interface is similar to that of an abstract class. The difference is that all methods and properties in an interface do not have concrete values; in other words, all methods in an interface are abstract methods. An interface mainly defines the structure of a class. An interface can restrict the interface of an object. An object can only match the interface if it contains all the properties and methods defined in the interface. At the same time, a class can implement an interface, and when implementing the interface, the class must protect all properties in the interface.
Example (checking object types):
interface Person{ name: string; sayHello():void; } function fn(per: Person){ per.sayHello(); } fn({name:'Sun Wukong', sayHello() {console.log(`Hello, I am ${this.name}`)}});
Example (implementation)
interface Person{ name: string; sayHello():void; } class Student implements Person{ constructor(public name: string) { } sayHello() { console.log('Hello, I am '+this.name); } }
4. Generics
When defining a function or class, there are cases where you cannot determine the specific type to be used (the return value, parameter, or property type cannot be determined). In this case, generics can play a role.
For example:
function test(arg: any): any{ return arg; }
In the above example, the test function has a parameter with an uncertain type, but it can be determined that the return value type and the parameter type are the same. Since the type is uncertain, both the parameter and return value use any, but it is clear that this is not appropriate. First, using any will disable TS's type checking, and second, this setting cannot reflect that the parameter and return value are of the same type.
Using generics:
function test<T>(arg: T): T{ return arg; }
Here,
<T>
is the generic type, and T is the name we give to this type (it doesn't have to be T). After setting the generic type, you can use T in the function to represent that type. So generics are actually easy to understand; they represent a certain type.So how do you use the function above?
Method one (direct use):
test(10)
You can directly pass parameters when using it, and the type will be automatically inferred by TS. However, sometimes the compiler cannot automatically infer, and you need to use the following method.
Method two (specifying type):
test<number>(10)
You can also manually specify the generic type after the function.
You can specify multiple generics at the same time, separated by commas:
function test<T, K>(a: T, b: K): K{ return b; } test<number, string>(10, "hello");
When using generics, you can treat generics as a normal class.
You can also use generics in classes:
class MyClass<T>{ prop: T; constructor(prop: T){ this.prop = prop; } }
In addition, you can also constrain the range of generics:
interface MyInter{ length: number; } function test<T extends MyInter>(arg: T): number{ return arg.length; }
Using T extends MyInter means that the generic T must be a subclass of MyInter. This does not have to be limited to interfaces; abstract classes can also apply.