类
类的定义
ES6新加了class关键字。 TypeScript中的类和JavaScript中的类非常相似,只是多了一些类型的限制。
在TypeScript中,定义类的语法格式如下:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
const person = new Person("Tom", 18);
person.sayName(); // 输出:Tom
在上面的代码中,我们定义了一个类Person,该类有两个属性name和age,一个构造函数和一个方法sayName。
在构造函数中,我们使用this关键字来指向当前对象,然后将传入的参数赋值给对象的属性。
在方法sayName中,我们使用this关键字来指向当前对象,然后输出对象的name属性。
最后,我们使用new关键字来创建一个person对象,然后调用对象的sayName方法,输出对象的name属性。
类的继承
面向对象编程三大特性封装、继承、多态。
封装
封装是指将数据和行为包装在一起,形成一个不可分割的整体,对外部隐藏对象的内部细节,只暴露必要的接口。
封装的好处是可以提高代码的可维护性和可复用性,同时可以保护对象的内部状态,防止外部直接修改对象的属性。
在JavaScript中,封装可以通过闭包、模块化等方式实现。
在TypeScript中,封装可以通过访问修饰符来实现,包括public、private和protected。
多态
在JavaScript中,多态可以通过函数重载、对象的动态绑定等方式实现。
在TypeScript中,多态可以通过接口、抽象类等方式实现。
继承
继承是指子类可以继承父类的属性和方法,同时可以重写父类的方法,实现代码的复用和扩展。
继承的好处是可以减少代码的重复,提高代码的可维护性和可扩展性。
在JavaScript中,继承可以通过原型链、构造函数等方式实现。
在TypeScript中,继承可以通过extends关键字来实现。
继承类
在TypeScript中,继承类的语法格式如下:
class Animal {
type: string;
constructor(type: string) {
this.type = type;
}
sayType() {
console.log(this.type);
}
}
class Cat extends Animal {
constructor(type: string) {
super(type);
}
sayType() {
console.log(`I am a ${this.type}`);
}
}
const cat = new Cat("cat");
cat.sayType(); // 输出:I am a cat
在上面的代码中,我们定义了一个类Animal,该类有一个属性type和一个方法sayType。
然后,我们定义了一个类Cat,该类继承自Animal类,并且重写了方法sayType。
在方法sayType中,我们使用super关键字来调用父类构造函数和方法,然后输出"I am a "和type属性。
最后,我们使用new关键字来创建一个cat对象,然后调用对象的sayType方法,输出"I am a cat"。
综上所述,继承类可以实现代码的复用和扩展,同时可以减少代码的重复,提高代码的可维护性和可扩展性。
多态
在面向对象编程中,多态是指同一种行为具有多种不同的表现形式,可以通过父类的引用指向子类的对象,实现代码的灵活性和扩展性。
在TypeScript中,多态可以通过接口、抽象类等方式实现。
接口多态
在TypeScript中,接口可以用来定义一组属性和方法的规范,然后可以通过实现接口来实现多态。
interface Animal {
type: string;
sayType(): void;
}
class Cat implements Animal {
type: string;
constructor(type: string) {
this.type = type;
}
sayType() {
console.log(`I am a ${this.type}`);
}
}
class Dog implements Animal {
type: string;
constructor(type: string) {
this.type = type;
}
sayType() {
console.log(`I am a ${this.type}`);
}
}
function say(animal: Animal) {
animal.sayType();
}
const cat = new Cat("cat");
const dog = new Dog("dog");
say(cat); // 输出:I am a cat
say(dog); // 输出:I am a dog
在上面的代码中,我们定义了一个接口Animal,该接口有一个属性type和一个方法sayType。
然后,我们定义了一个类Cat,该类实现了Animal接口,并且重写了方法sayType。
接着,我们定义了一个类Dog,该类实现了Animal接口,并且重写了方法sayType。
最后,我们定义了一个函数say,该函数的参数animal是Animal类型,然后调用animal的sayType方法。
我们使用new关键字来创建一个cat对象和一个dog对象,然后分别调用函数say,输出"I am a cat"和"I am a dog"。
综上所述,接口多态可以实现代码的灵活性和扩展性,同时可以减少代码的重复,提高代码的可维护性和可扩展性。
普通类实现多态
class Animal {
type: string;
constructor(type: string) {
this.type = type;
}
sayType() {
console.log(`I am a ${this.type}`);
}
}
class Cat extends Animal {
constructor(type: string) {
super(type);
}
}
class Dog extends Animal {
constructor(type: string) {
super(type);
}
}
function say(animal: Animal) {
animal.sayType();
}
const cat = new Cat("cat");
const dog = new Dog("dog");
say(cat); // 输出:I am a cat
say(dog); // 输出:I am a dog
抽象类实现多态
抽象类是一种特殊的类,它不能被实例化,只能被继承。抽象类可以包含抽象方法和非抽象方法。
抽象方法是一种没有实现的方法,它只有方法签名,没有方法体。抽象方法必须在子类中实现。
非抽象方法是一种有实现的方法,它可以被子类继承和重写,也可以被子类直接调用。
在TypeScript中,抽象类可以通过abstract关键字来定义,抽象方法可以通过abstract关键字来定义。
例如:
abstract class Animal {
type: string;
constructor(type: string) {
this.type = type;
}
abstract sayType(): void;
}
class Cat extends Animal {
constructor(type: string) {
super(type);
}
sayType() {
console.log(`I am a ${this.type}`);
}
}
class Dog extends Animal {
constructor(type: string) {
super(type);
}
sayType() {
console.log(`I am a ${this.type}`);
}
}
function say(animal: Animal) {
animal.sayType();
}
const cat = new Cat("cat");
const dog = new Dog("dog");
say(cat); // 输出:I am a cat
say(dog); // 输出:I am a dog
综上所述,普通类也可以实现多态,但是抽象类可以更好地实现多态,因为抽象类可以限制子类必须实现某些方法,从而提高代码的可维护性和可扩展性。
类的成员修饰符
在TypeScript中,类的修饰符有public、private和protected。
1. public修饰符
public修饰符表示该属性或方法是公共的,可以在类的内部和外部访问。
class Animal {
public type: string;
constructor(type: string) {
this.type = type;
}
public sayType() {
console.log(`I am a ${this.type}`);
}
}
const animal = new Animal("animal");
console.log(animal.type); // 输出:animal
animal.sayType(); // 输出:I am a animal
在上面的代码中,我们定义了一个类Animal,该类有一个公共属性type和一个公共方法sayType。
然后,我们使用new关键字来创建一个animal对象,并且访问animal的type属性和sayType方法,输出"animal"和"I am a animal"。
2. private修饰符
private修饰符表示该属性或方法是私有的,只能在类的内部访问,不能在类的外部访问。
class Animal {
private type: string;
constructor(type: string) {
this.type = type;
}
private sayType() {
console.log(`I am a ${this.type}`);
}
}
const animal = new Animal("animal");
console.log(animal.type); // 报错:Property 'type' is private and only accessible within class 'Animal'.
animal.sayType(); // 报错:Property 'sayType' is private and only accessible within class 'Animal'.
在上面的代码中,我们定义了一个类Animal,该类有一个私有属性type和一个私有方法sayType。
然后,我们使用new关键字来创建一个animal对象,并且访问animal的type属性和sayType方法,都会报错,因为它们都是私有的,只能在类的内部访问。
3.protected修饰符
protected修饰符表示该属性或方法是受保护的,只能在类的内部和子类中访问,不能在类的外部访问。
class Animal {
protected type: string;
constructor(type: string) {
this.type = type;
}
protected sayType() {
console.log(`I am a ${this.type}`);
}
}
class Cat extends Animal {
constructor(type: string) {
super(type);
}
public sayType() {
console.log(`I am a cat`);
}
}
const cat = new Cat("cat");
console.log(cat.type); // 报错:Property 'type' is protected and only accessible within class 'Animal' and its subclasses.
cat.sayType(); // 输出:I am a cat
在上面的代码中,我们定义了一个类Animal,该类有一个受保护的属性type和一个受保护的方法sayType。
然后,我们定义了一个类Cat,该类继承自Animal类,并且重写了sayType方法。
最后,我们使用new关键字来创建一个cat对象,并且访问cat的type属性和sayType方法,访问type属性会报错,因为它是受保护的,只能在Animal类和它的子类中访问,访问sayType方法会输出"I am a cat",因为它已经被重写了。
综上所述,类的修饰符可以控制类的属性和方法的访问权限,public表示公共的,private表示私有的,protected表示受保护的。
只读属性
在TypeScript中,可以使用readonly关键字来定义只读属性。
只读属性是指一旦被赋值后,就不能再被修改的属性。(准确上来说不能通过对象的属性来修改,可以使用类型断言或者类型转换来实现。)
class Animal {
readonly type: string;
constructor(type: string) {
this.type = type;
}
}
const animal = new Animal("animal");
console.log(animal.type); // 输出:animal
animal.type = "cat"; // 报错:Cannot assign to 'type' because it is a read-only property.
(animal as any).type = "cat";
console.log(animal.type); // 输出:cat
get的set
在TypeScript中,getter和setter是一种属性访问器,它们允许您读取和写入类中的私有成员。getter和setter是一对函数,getter函数用于获取属性值,setter函数用于设置属性值。以下是一个示例:
class Example {
private _name: string;
get name(): string {
return this._name;
}
set name(newName: string) {
this._name = newName;
}
}
const example = new Example();
example.name = "CursorBot";
console.log(example.name); // Output: "CursorBot"
上面的示例中,我们定义了一个名为Example的类,它具有一个名为_name的私有成员。
我们还定义了一个名为name的getter和setter,它们允许我们读取和写入_name成员。
在类的实例化过程中,我们可以使用setter函数设置name属性的值,然后使用getter函数获取该值。
类的静态成员
在 TypeScript 中,你可以使用 static
关键字来定义类的静态成员。静态成员是属于类本身的,而不是属于类的实例的。这意味着你可以在不创建类的实例的情况下访问静态成员。
下面是一个使用静态成员的示例:
class MyClass {
static myStaticProperty = 42;
static myStaticMethod() {
console.log('Hello, world!');
}
}
console.log(MyClass.myStaticProperty); // 输出 42
MyClass.myStaticMethod(); // 输出 "Hello, world!"
在这个例子中,我们定义了一个名为 MyClass的类,并在其中定义了一个静态属性 myStaticProperty
和一个静态方法 myStaticMethod。我们可以在不创建 MyClass的实例的情况下访问这些静态成员。
接口
在 TypeScript 中,接口是一种用于描述对象的形状的方式。接口可以描述对象的属性、方法和索引签名。你可以使用接口来定义函数的参数类型、返回值类型和类的类型。
下面是一个使用接口描述对象的示例:
interface MyObject {
myProperty: string;
myMethod(): void;
}
const myInstance: MyObject = {
myProperty: 'Hello, world!',
myMethod() {
console.log(this.myProperty);
}
};
myInstance.myMethod(); // 输出 "Hello, world!"
在这个例子中,我们定义了一个名为 MyObject的接口,它描述了一个具有 myProperty属性和 myMethod方法的对象的形状。
然后,我们创建了一个符合 MyObject接口的对象,并将其赋值给一个类型为 MyObject的变量 myInstance。
我们可以通过 myInstance访问对象的属性和方法。
索引类型
在 TypeScript 中,你可以使用索引类型来描述具有动态属性名称的对象的类型。索引类型可以用于描述对象的属性类型和方法参数类型。
下面是一个使用索引类型描述对象类型的示例:
interface MyObject {
[key: string]: number;
}
const myObject: MyObject = {
foo: 42,
bar: 1337 //在 JavaScript 中,对象的属性名称可以是字符串或符号。当你使用数字作为属性名称
}; //时,它会被自动转换为字符串
console.log(myObject.foo); // 输出 42
console.log(myObject.bar); // 输出 1337
使用接口来定义函数类型
在 TypeScript 中,你可以使用接口来定义函数的参数类型、返回值类型和类的类型。
下面是一个使用接口定义函数类型的示例:
interface MyFunction {
(arg1: string, arg2: number): boolean;
}
const myFunction: MyFunction = (arg1, arg2) => {
console.log(arg1, arg2);
return true;
};
myFunction('Hello, world!', 42); // 输出 "Hello, world!" 和 42
在这个例子中,我们定义了一个名为 MyFunction 的接口,它描述了一个具有两个参数(一个字符串和一个数字)和一个布尔返回值的函数的类型。然后,我们创建了一个符合 MyFunction 接口的函数,并将其赋值给一个类型为 MyFunction 的变量 myFunction。我们可以通过 myFunction 调用这个函数,并传递两个参数。
使用接口来定义类的类型
在 TypeScript 中,你可以使用接口来定义类的类型。接口可以描述类的实例部分和静态部分的类型。
下面是一个使用接口定义类类型的示例:
interface MyInterface {
myProperty: string;
myMethod(): void;
}
class MyClass implements MyInterface {
myProperty = 'Hello, world!';
myMethod() {
console.log(this.myProperty);
}
}
const myInstance: MyInterface = new MyClass();
myInstance.myMethod(); // 输出 "Hello, world!"
在这个例子中,我们定义了一个名为 MyInterface的接口,它描述了一个具有 myProperty属性和
myMethod方法的类的类型。
然后,我们定义了一个名为 MyClass的类,并实现了 MyInterface接口。
最后,我们创建了一个 MyClass的实例,并将其赋值给一个类型为 MyInterface的变量 myInstance。
我们可以通过 myInstance访问 MyClass的实例部分和静态部分的成员。
接口的继承
在 TypeScript 中,接口可以继承其他接口。这使得你可以将多个接口组合成一个更大的接口,从而使代码更加模块化和可重用。
下面是一个使用接口继承的示例:
interface Animal {
name: string;
eat(food: string): void;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
class Labrador implements Dog {
name: string;
breed: string;
constructor(name: string, breed: string) {
this.name = name;
this.breed = breed;
}
eat(food: string) {
console.log(`${this.name} is eating ${food}`);
}
bark() {
console.log(`${this.name} is barking`);
}
}
const myDog = new Labrador("Buddy", "Labrador Retriever");
myDog.eat("dog food"); // 输出 "Buddy is eating dog food"
myDog.bark(); // 输出 "Buddy is barking"
在这个例子中,我们定义了两个接口 Animal和 Dog。
Dog接口继承了 Animal接口,并添加了一个 breed属性和一个 bark方法。
然后,我们定义了一个名为 Labrador的类,它实现了 Dog接口。
最后,我们创建了一个 Labrador类的实例,并调用了它的 eat 和 bark方法。
交叉类型
在 TypeScript 中,交叉类型可以用于将多个类型组合成一个类型。交叉类型表示同时具有多个类型的值的类型。
下面是一个使用交叉类型的示例:
interface A {
a: string;
}
interface B {
b: number;
}
type C = A & B; //交叉 与
type D = A | B //联合 或
const c: C = {
a: "hello",
b: 42
};
const d: D = {
b:43
}
console.log(c.a); // 输出 "hello"
console.log(c.b); // 输出 42
接口的继承和交叉类型可以将多个接口组合成一个更大的接口,从而使代码更加模块化和可重用。
类的实现
在 TypeScript 中,接口可以用于描述对象的形状,包括对象的属性和方法。接口本身并不实现任何功能,它只是定义了一个类型。要使用接口,你需要创建一个符合接口定义的对象或类。
interface Person {
name: string;
age: number;
sayHello(): void;
}
class Student implements Person { //定义了一个名为 Student的类,它实现了 Person接口。
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
function sayHello(sayable: Person) {
sayable.sayHello()
}
sayHello(new Student("jk",18)) //Student实现了PerSon类,所以Student实例可以当函数入参。
const student = new Student("Alice", 20);
student.sayHello(); // 输出 "Hello, my name is Alice and I'm 20 years old."
就比如一个接口里面有一个函数
sayHello(): void;
会被经常调用。你就可以在多个类的定义上实现这个接口
class Student implements Person,然后定义这个函数,并在外部也定义这个函数,
函数的入参设置为那个接口类型。
function sayHello(sayable: Person) { sayable.sayHello() }
最后如果调用那个函数只需要传入实现接口的类的实例即可
sayHello(new Student("jk",18))。面向接口编程。
type和interface区别
在 TypeScript 中,interface和 type都可以用于定义类型。它们的主要区别在于:
-
interface可以被合并,而type不行。这意味着你可以定义多个同名的interface,它们会自动合并成一个接口。但是,如果你定义多个同名的type,它们会报错。 -
interface可以被 extends和 implements,而type不行。这意味着你可以使用interface来扩展其他接口或类,或者让类实现某个接口。但是,type不能这样做。 -
type可以使用联合类型、交叉类型和元组类型,而interface不行。这意味着你可以使用type来定义更复杂的类型,例如联合类型、交叉类型和元组类型。但是,interface不能这样做。
总的来说,interface更适合用于描述对象的形状,而 type更适合用于定义复杂的类型。但是,它们的使用场景有很大的重叠,具体使用哪个取决于你的个人喜好和项目需求。