类 - 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)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为
posted @ 2022-05-15 23:44  真的想不出来  阅读(49)  评论(0)    收藏  举报