5. 类

116 阅读7分钟

5. 类

5.1 如何定义类

  • "strictPropertyInitialization": true / 启用类属性初始化的严格检查/

  • name!:string


class Person{

name:string;

getName():void{

console.log(this.name);

}

}

let p1 = new Person();

p1.name = 'zhufeng';

p1.getName();


/**

* 当我们写一个类的时候,会得到2个类型

* 1. 构造函数类型的函数类型

* 2. 类的实例类型

*/

class Component {

static myName: string = '静态名称属性';

myName: string = '实例名称属性';

}

let com = Component;

//Component类名本身表示的是实例的类型

//ts 一个类型 一个叫值

//冒号后面的是类型

//放在=后面的是值

let c: Component = new Component();

let f: typeof Component = com;

5.2 存取器

  • 在 TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为

  • 构造函数

  • 主要用于初始化类的成员变量属性

  • 类的对象创建时自动调用执行

  • 没有返回值


class User {

myname:string;

constructor(myname: string) {

this.myname = myname;

}

get name() {

return this.myname;

}

set name(value) {

this.myname = value;

}

}

let user = new User('zhufeng');

user.name = 'jiagou';

console.log(user.name);

上面代码编译后


"use strict";

var User = /** @class */ (function () {

function User(myname) {

this.myname = myname;

}

Object.defineProperty(User.prototype, "name", {

get: function () {

return this.myname;

},

set: function (value) {

this.myname = value;

},

enumerable: true,

configurable: true

});

return User;

}());

var user = new User('zhufeng');

user.name = 'jiagou';

console.log(user.name);

5.3 参数属性


class User {

constructor(public myname: string) {}

get name() {

return this.myname;

}

set name(value) {

this.myname = value;

}

}

let user = new User('zhufeng');

console.log(user.name);

user.name = 'jiagou';

console.log(user.name);

5.4 readonly

  • readonly修饰的变量只能在构造函数中初始化

  • 在 TypeScript 中,const 是常量标志符,其值不能被重新分配

  • TypeScript 的类型系统同样也允许将 interface、type、 class 上的属性标识为 readonly

  • readonly 实际上只是在编译阶段进行代码检查。而 const 则会在运行时检查(在支持 const 语法的 JavaScript 运行时环境中)


class Animal {

public readonly name: string

constructor(name:string) {

this.name = name;

}

changeName(name:string){

this.name = name;//Cannot assign to 'name' because it is a read-only property

}

}

let a = new Animal('zhufeng');

a.changeName('jiagou');

5.5 继承

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性

  • 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑

  • super可以调用父类上的方法和属性


class Person {

name: string;//定义实例的属性,默认省略public修饰符

age: number;

constructor(name:string,age:number) {//构造函数

this.name=name;

this.age=age;

}

getName():string {

return this.name;

}

setName(name:string): void{

this.name=name;

}

}

class Student extends Person{

no: number;

constructor(name:string,age:number,no:number) {

super(name,age);

this.no=no;

}

getNo():number {

return this.no;

}

}

let s1=new Student('zfpx',10,1);

console.log(s1);

5.6 类里面的修饰符


class Father {

public name: string; //类里面 子类 其它任何地方外边都可以访问

protected age: number; //类里面 子类 都可以访问,其它任何地方不能访问

private money: number; //类里面可以访问, 子类和其它任何地方都不可以访问

constructor(name:string,age:number,money:number) {//构造函数

this.name=name;

this.age=age;

this.money=money;

}

getName():string {

return this.name;

}

setName(name:string): void{

this.name=name;

}

}

class Child extends Father{

constructor(name:string,age:number,money:number) {

super(name,age,money);

}

desc() {

console.log(`${this.name} ${this.age} ${this.money}`);

}

}

let child = new Child('zfpx',10,1000);

console.log(child.name);

console.log(child.age);

console.log(child.money);

5.7 静态属性 静态方法


class Father {

static className='Father';

static getClassName() {

return Father.className;

}

public name: string;

constructor(name:string) {//构造函数

this.name=name;

}

}

console.log(Father.className);

console.log(Father.getClassName());

5.8 装饰器

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为

  • 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器

  • 装饰器的写法分为普通装饰器和装饰器工厂


class Person{

say() {

console.log('hello')

}

}

function Person() {}

Object.defineProperty(Person.prototype, 'say', {

value: function() { console.log('hello'); },

enumerable: false,

configurable: true,

writable: true

});

5.8.1 类装饰器

  • 类装饰器在类声明之前声明,用来监视、修改或替换类定义

namespace a {

//当装饰器作为修饰类的时候,会把构造器传递进去

function addNameEat(constructor: Function) {

constructor.prototype.name = "zhufeng";

constructor.prototype.eat = function () {

console.log("eat");

};

}

@addNameEat

class Person {

name!: string;

eat!: Function;

constructor() {}

}

let p: Person = new Person();

console.log(p.name);

p.eat();

}

namespace b {

//还可以使用装饰器工厂

function addNameEatFactory(name:string) {

return function (constructor: Function) {

constructor.prototype.name = name;

constructor.prototype.eat = function () {

console.log("eat");

};

};

}

@addNameEatFactory('zhufeng')

class Person {

name!: string;

eat!: Function;

constructor() {}}

let p: Person = new Person();

console.log(p.name);

p.eat();

}

namespace c {

//还可以替换类,不过替换的类要与原类结构相同

function enhancer(constructor: Function) {

return class {

name: string = "jiagou";

eat() {

console.log("吃饭饭");

}

};

}

@enhancer

class Person {

name!: string;

eat!: Function;

constructor() {}}

let p: Person = new Person();

console.log(p.name);

p.eat();

}

5.8.2 属性装饰器

  • 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数

  • 属性装饰器用来装饰属性

  • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象

  • 第二个参数是属性的名称

  • 方法装饰器用来装饰方法

  • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象

  • 第二个参数是方法的名称

  • 第三个参数是方法描述符


namespace d {

//修饰实例属性

function upperCase(target: any, propertyKey: string) {

let value = target[propertyKey];

const getter = function () {

return value;

}

// 用来替换的setter

const setter = function (newVal: string) {

value = newVal.toUpperCase()

};

// 替换属性,先删除原先的属性,再重新定义属性

if (delete target[propertyKey]) {

Object.defineProperty(target, propertyKey, {

get: getter,

set: setter,

enumerable: true,

configurable: true

});

}

}

//修饰实例方法

function noEnumerable(target: any, property: string, descriptor: PropertyDescriptor) {

console.log('target.getName', target.getName);

console.log('target.getAge', target.getAge);

descriptor.enumerable = true;

}

//重写方法

function toNumber(target: any, methodName: string, descriptor: PropertyDescriptor) {

let oldMethod = descriptor.value;

descriptor.value = function (...args: any[]) {

args = args.map(item => parseFloat(item));

return oldMethod.apply(this, args);

}

}

class Person {

@upperCase

name: string = 'zhufeng'

public static age: number = 10

constructor() { }

@noEnumerable

getName() {

console.log(this.name);

}

@toNumber

sum(...args: any[]) {

return args.reduce((accu: number, item: number) => accu + item, 0);

}

}

let p: Person = new Person();

for (let attr in p) {

console.log('attr=', attr);

}

p.name = 'jiagou';

p.getName();

console.log(p.sum("1", "2", "3"));

}

5.8.3 参数装饰器

  • 会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据

  • 第1个参数对于静态成员是类的构造函数,对于实例成员是类的原型对象

  • 第2个参数的名称

  • 第3个参数在函数列表中的索引


namespace e {

interface Person {

age: number;

}

function addAge(target: any, methodName: string, paramsIndex: number) {

console.log(target);

console.log(methodName);

console.log(paramsIndex);

target.age = 10;

}

class Person {

login(username: string, @addAge password: string) {

console.log(this.age, username, password);

}

}

let p = new Person();

p.login('zhufeng', '123456')

}

5.8.4 装饰器执行顺序

  • 有多个参数装饰器时:从最后一个参数依次向前执行

  • 方法和方法参数中参数装饰器先执行。

  • 类装饰器总是最后执行

  • 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行

  • 类比React组件的componentDidMount 先上后下、先内后外


namespace e {

function Class1Decorator() {

return function (target: any) {

console.log("类1装饰器");

}

}

function Class2Decorator() {

return function (target: any) {

console.log("类2装饰器");

}

}

function MethodDecorator() {

return function (target: any, methodName: string, descriptor: PropertyDescriptor) {

console.log("方法装饰器");

}

}

function Param1Decorator() {

return function (target: any, methodName: string, paramIndex: number) {

console.log("参数1装饰器");

}

}

function Param2Decorator() {

return function (target: any, methodName: string, paramIndex: number) {

console.log("参数2装饰器");

}

}

function PropertyDecorator(name: string) {

return function (target: any, propertyName: string) {

console.log(name + "属性装饰器");

}

}

@Class1Decorator()

@Class2Decorator()

class Person {

@PropertyDecorator('name')

name: string = 'zhufeng';

@PropertyDecorator('age')

age: number = 10;

@MethodDecorator()

greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) { }

}

}

/**

name属性装饰器

age属性装饰器

参数2装饰器

参数1装饰器

方法装饰器

类2装饰器

类1装饰器

*/

5.9 抽象类

  • 抽象类描述一种抽象的概念,无法被实例化,只能被继承

  • 无法创建抽象类的实例

  • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现


abstract class Animal {

name!:string;

abstract speak():void;

}

class Cat extends Animal{

speak(){

console.log('喵喵喵');

}

}

let animal = new Animal();//Cannot create an instance of an abstract class

animal.speak();

let cat = new Cat();

cat.speak();

5.10 抽象方法

  • 抽象类和方法不包含具体实现,必须在子类中实现

  • 抽象方法只能出现在抽象类中

  • 子类可以对抽象类进行不同的实现


abstract class Animal{

abstract speak():void;

}

class Dog extends Animal{

speak(){

console.log('小狗汪汪汪');

}

}

class Cat extends Animal{

speak(){

console.log('小猫喵喵喵');

}

}

let dog=new Dog();

let cat=new Cat();

dog.speak();

cat.speak();

5.11 重写(override) vs 重载(overload)

  • 重写是指子类重写继承自父类中的方法

  • 重载是指为同一个函数提供多个类型定义


class Animal{

speak(word:string):string{

return '动作叫:'+word;

}

}

class Cat extends Animal{

speak(word:string):string{

return '猫叫:'+word;

}

}

let cat = new Cat();

console.log(cat.speak('hello'));

//--------------------------------------------

function double(val:number):number

function double(val:string):string

function double(val:any):any{

if(typeof val == 'number'){

return val *2;

}

return val + val;

}

let r = double(1);

console.log(r);

继承 vs 多态

  • 继承(Inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性

  • 多态(Polymorphism)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为


class Animal{

speak(word:string):string{

return 'Animal: '+word;

}

}

class Cat extends Animal{

speak(word:string):string{

return 'Cat:'+word;

}

}

class Dog extends Animal{

speak(word:string):string{

return 'Dog:'+word;

}

}

let cat = new Cat();

console.log(cat.speak('hello'));

let dog = new Dog();

console.log(dog.speak('hello'));