TypeScript类

218 阅读7分钟

定义类

class Person {
    name:string;
    age:number;
    getName():void{
        console.log(this.name);
    };
    constructor() {
        this.age = 18
    }
}
let p1 = new Person();
p1.name='张三'
p1.getName();
console.log(p1.name)
console.log(p1.age)

存取器 getter setter

  • 在 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('张三');
user.name = '李四'; 
console.log(user.name);

参数属性 public

  • 把属性赋给class实例,变为实例属性
class User {
        constructor(public name: string) {}
    }
    
    let user = new User('1111');
    console.log(user.name);
    user.name = '111123333'; 
    console.log(user.name);

只读属性 readonly

  • readonly修饰的变量只能在构造函数中初始化
class Animal {
       public readonly name: string
       constructor(name:string) {
           this.name = name;
       }
       changeName(name:string){
           // this.name = name;//报错 只读属性 只能在构造函数中赋值 或者初始化赋值
       }
   }
   
   let a = new Animal('dog');
   a.changeName('cat');

继承 class 子类 extends 父类

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
  • 子类不可继承父类的静态方法和属性
  • 将子类公用的方法抽出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
  • 派生类的构造函数必须包含 super 调用
class User {
      name:string;
      age: number;
      sex:string = 'man';
      constructor(name: string, age:number) {
          this.name = name;
          this.age = age;
      }
      getName() {
          return this.name
      }
      setName(newName: string){
          this.name = newName
      }
      
  }
  class Student extends User {
      stuNo: number;
      constructor(name: string, age: number, stuNo: number){//派生类的构造函数必须包含 "super" 调用
          super(name, age)
          this.stuNo = stuNo
      }
      getStuNo(){
          return this.stuNo
      }
      setStuNo(newStuNo: number){
          this.stuNo = newStuNo
      }
  }
  let s = new Student('xiuli',18,1)
  console.log(s.sex)

类中的修饰符

  • public 自己 自己的子类 和其他类都可以访问
  • protected 受保护的 自己和自己的子类能访问, 其他类不能访问
  • private 私有的 只能自己访问,自己的子类不能访问,其他更不行
class User {
        name:string;
        protected age: number;
        protected sex:string = 'man';
        private amount: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 User {
        stuNo: number;
        constructor(name: string, age: number, stuNo: number){//派生类的构造函数必须包含 "super" 调用
            super(name, age)
            this.stuNo = stuNo
        }
        getStuNo(){
            // console.log(this.amount)
            return this.stuNo
        }
        setStuNo(newStuNo: number){
            this.stuNo = newStuNo
        }
    }
    let s = new Student('xiuli',18,1)
    // console.log(s.sex)

静态属性 静态方法 static

  • static 类属性; 其他属性属于实例属性prototype
class User {
        name:string;
        age: number;
        sex:string = 'man';
        static amount:number;
        constructor(name: string, age:number) {
            this.name = name;
            this.age = age;
        }
        static getAmount () {
            return User.amount
        }
        getName() {
            return this.name
        }
        setName(newName: string){
            this.name = newName
        }
    }
    let a  = new User('x',1)
    a.name = 'qq'
    // a.amount = 1;//报错
    User.amount = 2

    console.log(User.getAmount())

装饰器

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或者参数上,可以修改类的行为
  • 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
  • 装饰器的写法分为普通装饰器和装饰器工厂(装饰器带参数,在原有装饰器的基础上,设置外部变量)

类装饰器

  • 类装饰器在类声明之前声明,用来监视、修改或替换类定义
namespace a {
    interface Person {
        name: string;
        eat: any
    }
    function enhancer(target: any) {
        target.prototype.name = 'xiuli';
        target.prototype.eat = function () {
            console.log('eat');
        }
    }
    @enhancer
    class Person {
        constructor() { }
    }
    let p = new Person();
    console.log(p.name);
    p.eat();
}

namespace b {
    interface Person {
        name: string;
        eat: any
    }
    function enhancer(name: string) {
        return function enhancer(target: any) {
            target.prototype.name = name;
            target.prototype.eat = function () {
                console.log('eat');
            }
        }
    }

    @enhancer('xiuli')
    class Person {
        constructor() { }
    }
    let p = new Person();
    console.log(p.name);
    p.eat();
}

namespace c {
    interface Person {
        name: string;
        eat: any
    }
    function enhancer(target: any) {
        return class {
            name: string = 'li'
            eat() {
                console.log('吃饭饭');
            }
        }
    }
    @enhancer
    class Person {
        constructor() { }
    }
    let p = new Person();
    console.log(p.name);
    p.eat();
}

属性装饰器 方法装饰器

  • 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数
  • 属性装饰器用来装饰属性
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
    • 第二个参数是属性的名称
  • 方法装饰器用来装饰方法
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是方法的名称
    • 第三个参数是方法描述符
  • Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
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,//当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。
                configurable: true,//当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
            });
        }
    }
    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 = new Person();
    for (let attr in p) {
        console.log('attr=', attr);
    }
    console.log('static:age', Person.age)
    p.name = 'jiagou';
    p.getName();
    console.log(p.sum("1", "2", "3"));
}

执行结果

target.getName function () {
            console.log(this.name);
        }
target.getAge undefined
attr= getName
attr= sum
attr= name
static:age 10
JIAGOU
6

参数装饰器

  • 会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是函数的名称
    • 第三个参数是在函数列表中的索引
namespace d {
    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('xiuli', '123456')
}

运行结果

Person { login: [Function] }
login
1
10 'xiuli' '123456'

装饰器执行顺序

  • 有多个参数装饰器时:从最后一个参数依次向前执行
  • 方法和方法参数:参数装饰器先执行
  • 类装饰器总是最后执行
  • 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法的一部分,所以参数会一致紧挨着方法执行
  • 方法中的参数装饰器:从最后一个参数一次向前执行
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 = 'xiuli';
        @PropertyDecorator('age')
        age: number = 10;
        @MethodDecorator()
        greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
    }
}

执行结果

name属性装饰器
age属性装饰器
方法装饰器
参数2装饰器
参数1装饰器
类2装饰器
类1装饰器

抽象类

  • 描述一种抽象的概念,无法被实例化,只能被继承
  • 无法创建抽象类的实例
  • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
abstract class Animal {
    name:string;
    setName(){
        this.name = '狗'
    }
    abstract getName(): string
}
class Cat extends Animal{
    getName(): string {
        return this.name
    }
}
let cat = new Cat();
cat.name = '猫'
cat.setName()
console.log(cat.getName())
// let a = new Animal();//报错 抽象类不能被实例化

抽象方法

  • 抽象类和方法不包含具体实现,必须在子类中实现
  • 抽象方法只能出现在抽象类中
  • 子类可以对抽象类进行不同的实现
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();

重写(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 多态

  • 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态:由继承所产生了相关的不同的类,对同一个方法可以有不同的行为
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'));