类 - 6
类
1. 如何定义类
"strictPropertylnitialization": true 启用类属性初始化的严格检查
name!: string
namespace a { // 设置独立的命名空间,防止类名重复而报错
class Person{
name: string = "ruhua"; // 定义完属性后必须赋值,可以直接赋值,也可以在 constructor 中赋值
age: number;
constructor() {
this.age = 18;
};
};
let p1 = new Person();
console.log(p1.name, p1.age);
}
2. 存取器 getter setter
- 在 TS 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为
- 构造函数
- 主要用于初始化类的成员变量属性
- 类的对象创建时自动调用执行
- 没有返回值
namespace b {
class Person{
myname: string;
constructor(name: string) {
this.myname = name;
};
get name() {
return this.myname;
};
set name(newVal: string) {
this.myname = newVal.toUpperCase();
};
};
let p = new Person("ruhua");
console.log(p.name); // "ruhua"
p.name = "xiaomei";
console.log(p.name); // "XIAOMEI"
};
3. 参数的属性
public(公开) 将参数挂在 this 上,赋值给实例。
namespace c {
class Person{
constructor(public name: string) {
};
let p = new Person("ruhua");
console.log(p.name); // "ruhua"
};
/*
等同这样写
namespace c {
class Person{
myname: string;
constructor(name: string) {
this.myname = name;
};
};
};
*/
4. readonly
- readonly 修饰的变量只能在 构造函数 中初始化
- 在 TS 中,const 是 常量 标识符,其值不能被重新分配
- TS 的类型系统同样也允许 interface、type、class 上的属性标识为 readonly
- readonly 实际上只是在 编译 阶段进行代码检查,而 const 则会在 运行时 检查(在支持 const 语法的 JS 运行时环境中)
namespace c {
class Person {
constructor(public readonly name: string) {
}
}
let p = new Person("ruhua");
// p.name = "xiaomei" // 报错,只读属性不能修改
}
5. 继承
- 子类继承父类后,子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
- 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
- super 可以调用父类上的方法和属性
namespace d {
class Person {
name: string;
age: number;
constructor(name: string, age:number) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
};
setName(newName: string) {
this.name = newName;
};
};
class Student extends Person {
stuNo: number;
constructor(name: string, age:number, stuNo: number) {
super(name, age);
this.stuNo = stuNo;
}
getStuNo() {
return this.stuNo;
};
setStuNo(newStuNo: number) {
this.stuNo = newStuNo;
};
};
let s = new Student("ruhua", 10, 1);
}
6 访问修饰符
- public(公开的,"自己","子类",和 "实例" 都可以访问)
- protected(受保护的,"自己","子类" 可以访问,"实例" 不能访问)
- private(私有的,只能 "自己" 访问,"子类" 和 "实例" 不行)
namespace d {
class Person {
public name: string; // public(公开的,自己,自己的子类,和其它类 都可以访问)
protected age: number;
private amount: number;
constructor(name: string, age:number, amount: number) {
this.name = name;
this.age = age;
this.amount = amount;
}
getName() {
return this.name;
};
setName(newName: string) {
this.name = newName;
};
};
class Student extends Person {
stuNo: number;
constructor(name: string, age:number, amount: number, stuNo: number) {
super(name, age, amount);
this.stuNo = stuNo;
}
getStuNo() {
console.log(this.name);
console.log(this.age);
// console.log(this.amount); // 报错:属性“amount”为私有属性,只能在类“Person”中访问
return this.stuNo;
};
setStuNo(newStuNo: number) {
this.stuNo = newStuNo;
};
};
let s = new Student("ruhua", 10, 1, 10);
console.log(s.name);
// console.log(s.age); // 报错:age 是受保护的,只能在类“Person”及其子类中被访问
// console.log(s.amount); // 报错:属性“amount”为私有属性,只能在类“Person”中访问
}
7 static 静态属性 静态方法
只能给类自身使用,不会被继承,不能被 实例 使用。
namespace e {
class Person {
static type = "Student";
static getType() {
return this.type;
}
};
console.log(Person.type);
console.log(Person.getType());
}
8 装饰器
- 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为
- 常见的装饰器有类装饰器、属性装饰器、方法装饰器、参数装饰器
- 装饰器的写法分为普通装饰器和装饰器工厂
8.1 类装饰器
类装饰器在类声明之前声明,用来监视、修改或替换类定义
// 通过装饰器,在类的外部,给类增加属性(增强属性)
namespace a {
interface Person {
xx: string;
yy: string;
}
function enhancer(target: any) {
target.prototype.xx = "xx"
target.prototype.yy = "yy"
}
@enhancer
class Person { // 需要去tsconfig.json,配置 'experimentalDecorators: true',标识允许使用装饰器
constructor() { }
}
let p = new Person();
console.log(p.xx);
console.log(p.yy);
}
// 把类整个替换(增强类)
/*
Person 类只有一个 name 属性,现在将它替换成 Child 类,多了一个 age 属性,、
实例 p 继承增强后的 Person,实质上是继承了 Child ,同时获得了 name 和 age 属性
*/
namespace b {
interface Person {
age: number;
}
function enhancer(target: any) {
return class Child extends Person {
public age: number = 10
}
}
@enhancer
class Person {
name: string = "Person";
constructor() { }
}
let p = new Person();
console.log(p.age, p.name);
}
8.1 属性装饰器
- 属性装饰器表达式会在运行时当做函数被调用,传入下列2个参数
- 属性装饰器用来装饰属性
- 第一个参数对于静态成员来说是类的改造函数,对于实例成员是类的原型对象
- 第二个参数是属性的名称
- 方法装饰器用来修饰方法
- 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数是方法的名称
- 第三个参数是方法描述符
// 属性装饰器
// 通过属性装饰器,给Person{},装饰属性|方法
namespace c {
// target 如果装饰的是一个普通属性的话,那么这个target指向类的原型 Person.prototype
// target 装饰的是一个类的属性 static,那么这个target指向类
function uperCase(target: any, propertyName: string) {
// 装饰属性,有两个参数`function(构造函数|原型对象, 属性的名称)`
let value = target[propertyName];
const getter = () => value;
const setter = (newVal: string) => {
value = newVal.toLocaleUpperCase();
};
delete target[propertyName];
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true, // 是否可枚举
configurable: true, // 是否可配置
})
};;
function methodEnumerable(flag: boolean) {
return function (target: any, methodName: string, propertyDescriptor: PropertyDescriptor) {
// 在 方法描述符 中规定改方法是否可枚举
propertyDescriptor.enumerable = flag;
};
};
function setAge(age: number) {
return function(target: any, methodName: string, propertyDescriptor: PropertyDescriptor) {
/* 1. 装饰函数,有三个参数`function(构造函数|原型对象, 方法的名称, 方法描述符)`
2. 装饰一个 static 属性|方法,此时 target 指向类 Person。【如果是非 static 属性|方法,target 会指向原型Person.prototype】
*/
target.age = age;
};
};
function toNumber(target: any, methodName: string, propertyDescriptor: PropertyDescriptor) {
let oldMethod = propertyDescriptor.value; // 获取到 sum 函数,赋给 oldMethod,此时 sum 函数的 this 是 undefined
propertyDescriptor.value = function(...args: any[]) { // 给原来的 sum 函数附址一个新的函数
args = args.map(item => parseFloat(item)); // 将传入的值,全部转成,数字类型
console.log(111, this, args); // 这里的 this 指向 p
/* 这里直接 return oldMethod(...args) 也是可以的,
最好让 oldMethod 函数的 this 指向 p,因为 sum 函数的 this,本来就是指向 p 的。
*/
return oldMethod.apply(this, args)
}
}
class Person {
static age: number;
@uperCase // 用装饰器装饰 name 属性,使其被查看的时候显示大写
name: string = "ruhua";
@methodEnumerable(true) // 用装饰器装饰 getName 方法,控制其是否可被枚举
getName() { console.log("getName"); }
@setAge(100)
static getAge() {};
@toNumber
sum(...args: any[]) {
console.log(222, this, args);
return args.reduce((accu, item) => accu + item, 0)
}
};
let p = new Person();
p.name = "xiaomei";
// 期望 console.log(p.name) 输出大写的 XIAOMEI
console.log(p.name) // XIAOMEI
for (let k in p) {
console.log("k", k); // 检查 getName 方法设置可枚举,是否成功。
}
// 通过装饰器,使 age 得到赋值
console.log(Person.age);
// 期望 输出的是 数字相加后的结果,而不是字符串相加
console.log(p.sum(1, "2", "3"))
}
8.2 参数装饰器
// 参数装饰器 装饰方法参数的
namespace d {
interface Person {
age: number
}
function addAge (target: any, methodName: string, paramsIndex: number) {
// function toBase (Person.prototype, login, 1) {}
target.age = 10
}
class Person{
// 现在需要将 password 通过装饰,使 Person 扩展出 age 属性并赋值 10
// login(userame: string, password: string) {
// console.log(userame, password)
// }
login(userame: string, @addAge password: string) {
console.log(userame, password)
console.log(this.age)
}
};
let p = new Person();
p.login("ruhua", "123")
}
8.3 装饰器执行顺序
- 有多个参数修饰符器时:从最后一个参数依次向前执行
- 方法和方法参数中,参数装饰器先执行
- 类装饰器总是最后执行
- 方法和属性装饰器,谁在前面谁先执行,应为参数属于方法的一部分,所以参数会一直紧紧挨着方法执行
- 如果有多个同一种的修饰器,先执行后写的,【比如:有多个类装饰器,会由近到远的顺序执行的【与类靠的最近的先执行,往外执行依次】
namespace e {
function class1Decorator(target: any) {
console.log("class1Decorator");
};
function class2Decorator(target: any) {
console.log("class2Decorator");
};
@class1Decorator
@class2Decorator
class Person{
};
/**
class2Decorator
class1Decorator
*/
}8
9 抽象类 abstract
- 抽象描述一种抽象的概念,无法被实例化,只能被继承
- 无法创建抽象类的实例
- 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
abstract class Animal {
name: string = "猫";
abstract getName(): string;
}
class Cat extends Animal {
getName(): string {
return this.name
}
}
let cat = new Cat();
console.log(cat.getName());
| 概念 | 定义用的关键字 |
|---|---|
| 访问控制修饰符 | private protected public |
| 只读属性 | readonly |
| 静态属性 | static |
| 抽象类、抽象方法 | abstract |
10 抽象类 vs 接口
- 不同类之间公有的属性或方法,可以抽象成一个接口(Interface)
- 而抽象类是供其它类继承的基础,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,即不提供方法的实现,也不为属性进行初始化
- 一个类可以继承一个类或者抽象类,也可以实现(implements)多个接口
- 抽象类也可以实现接口
// 定义接口 interface
// 有两个作用:
// 1. 可以用来描述有哪些属性,属性是什么类型
interface Point {
x: number;
y: number;
}
let point: Point = { x: 0, y: 0 }
// 2. 还可以用来描述行为的抽象
interface Speakable {
speak(): void // 不使用关键字,因为接口里面不能放实现,只能放定义,所有的方法都是抽象的
}
interface Eatable {
eat(): void // 不使用关键字,因为接口里面不能放实现,只能放定义,所有的方法都是抽象的
}
// 类可以实现多个接口,但只能继承一个父类
class Person implements Speakable, Eatable {
speak() { };
eat() { }
}
let p = new Person();
11 抽象方法
- 抽象类和方法不包含具体实现,必须在子类中实现
- 抽象方法只能出现在抽象类中
- 子类可以对抽象类进行不同的实现
12 重写(override)vs 重载(overload)
- 重写是指子类重写继承自父类中的方法
- 重载是指为同一个函数提供多个类型定义(在《函数 - 5》里有介绍)
namespace b {
// 重写:子类重新实现并覆盖父类中的方法
class Animal {
constructor() {
}
speak() {
console.log("动物叫")
}
};
class Cat extends Animal {
speak() {
console.log("喵喵喵")
}
}
let cat = new Cat();
cat.speak();
class Dog extends Animal {
constructor() {
super() // 这里的 super 指向 Animal
}
speak() {
console.log("汪汪汪")
// 这里的 super 指向 Animal.prototype
super.speak(); // 通过 super 调用父类的方法
}
}
let dog = new Dog();
dog.speak()
}
13 继承 vs 多态
- 继承(Inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为

浙公网安备 33010602011771号