【TypeScript】ts中的Classes使用方法介绍

【TypeScript】ts中的Classes使用方法介绍

码农世界 2024-05-26 前端 80 次浏览 0个评论

简言

TypeScript 完全支持 ES2015 中引入的类关键字。

与 JavaScript 语言的其他功能一样,TypeScript 添加了类型注解和其他语法,允许您表达类与其他类型之间的关系。

class类是一个较重要的知识。

Classes

类声明

ts中类的声明和js中的高度相似。

类定义:以class关键字声明,后面加类名(大驼峰),之后是花括号包裹类体,constructor构造函数可以不定义。

class Point {
  x = 0
  y = 0
}
const pt = new Point()
console.log(pt.x,pt.y);
class Point2 {
  x : number
  y : number
  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }
}
const pt2 = new Point2(1,1)
console.log(pt2.x,pt2.y);

就像使用 const、let 和 var 一样,类属性的初始化器将用于推断其类型,或者显示定义类型。

类属性

类属性和其他变量定义类型一样,默认可见性是public(公共的)。

若是想只读,前面需要加 readonly修饰,这样在构造函数之外无法赋值。

class Greeter {
  readonly name: string = "world";
  name2 = 'zsk'
  constructor(otherName?: string) {
    if (otherName !== undefined) {
      this.name = otherName;
    }
  }
}

类方法

类的函数属性称为方法。方法可以使用与函数和构造函数相同的类型注解,默认可见性是public(公共的)。

除了标准的类型注解,TypeScript 没有为方法添加任何新内容。

class Point {
  x = 10;
  y = 10;
 
  scale(n: number): void {
    this.x *= n;
    this.y *= n;
  }
}

类中的方法前面可以加set和get修饰。

TypeScript 对访问器有一些特殊的推理规则:

  • 如果存在 get 但没有 set,则属性自动为只读属性。
  • 如果未指定设置器(setters)参数的类型,则根据获取器(getters)的返回类型进行推断。
  • 获取器和设置器必须具有相同的成员可见性。
    class Thing {
      _size = 0;
     
      get size(): number {
        return this._size;
      }
     
      set size(value: string | number | boolean) {
        let num = Number(value);
     
        // Don't allow NaN, Infinity, etc
     
        if (!Number.isFinite(num)) {
          this._size = 0;
          return;
        }
     
        this._size = num;
      }
    }
    

    实现和继承

    与其他具有面向对象特性的语言一样,JavaScript 中的类可以从基类继承。

    实现 implements

    您可以使用 implements 子句来检查类是否满足特定接口。如果类未能正确实现该接口,则会出错:

    interface Pingable {
      ping(): void;
    }
     
    class Sonar implements Pingable {
      ping() {
        console.log("ping!");
      }
    }
    //errors
    class Ball implements Pingable {
     pong() {
        console.log("pong!");
      }
    }
    

    类也可以实现多个接口,例如类 C 实现 A、B {。

    另外,implements 子句只是检查类是否可以被视为接口类型。它根本不会改变类或其方法的类型。

    interface Checkable {
      check(name: string): boolean;
    }
     
    class NameChecker implements Checkable {
      check(s) {
      // Notice no error here
        return s.toLowerCase() === "ok";
      }
    }
    

    继承 extends

    类可以从基类扩展而来。派生类拥有基类的所有属性和方法,还可以定义其他成员。

    class Animal {
      move() {
        console.log("Moving along!");
      }
    }
     
    class Dog extends Animal {
      woof(times: number) {
        for (let i = 0; i < times; i++) {
          console.log("woof!");
        }
      }
    }
     
    const d = new Dog();
    // Base class method
    d.move();
    // Derived class method
    d.woof(3);
    

    派生类也可以覆盖基类的字段或属性。您可以使用 super. 语法访问基类方法。

    TypeScript 强制规定派生类始终是其基类的子类型。

    例如,下面是重写方法的合法方式:

    class Base {
      greet() {
        console.log("Hello, world!");
      }
    }
     
    class Derived extends Base {
      greet(name?: string) {
        if (name === undefined) {
          super.greet();
        } else {
          console.log(`Hello, ${name.toUpperCase()}`);
        }
      }
    }
     
    const d = new Derived();
    d.greet();
    d.greet("reader");
    // Alias the derived instance through a base class reference
    const b: Base = d;
    // No problem
    b.greet();
    

    派生类必须遵守符合(覆盖)基类的的类型。

    执行顺序:

    • 基类字段初始化
    • 基类构造函数运行
    • 派生类字段初始化
    • 运行派生类构造函数

      类成员可见性

      类属性和类函数都是类成员。

      您可以使用 TypeScript 来控制某些方法或属性是否对类外的代码可见。

      可见性分为以下三种:

      • public —— 类成员的默认可见性为 public。公共成员可以在任何地方被访问,由于 public 已是默认的可见性修饰符,因此您无需在类成员上写入该修饰符,但出于样式/可读性的考虑,您可能会选择这样做。
      • protected —— 受保护成员只对其所声明类的子类可见。
      • private —— 私有的,只能自己访问。private 和 protected 类似,但不允许子类访问该成员。

        与 TypeScript 类型系统的其他方面一样,private 和 protected 仅在类型检查过程中执行。这意味着,JavaScript 运行时构造(如 in 或简单的属性查询)仍可访问私有或受保护成员。

        private 还允许在类型检查时使用括号符号进行访问。这使得单元测试等工作更容易访问声明为私有的字段,但缺点是这些字段是软私有的,不能严格执行隐私保护(这个感觉是弊端,最好不要这样做)。

        class MySafe {
          private secretKey = 12345;
        }
         
        const s = new MySafe();
        // OK
        console.log(s["secretKey"]);
        

        静态成员

        类可能有静态成员。这些成员与类的特定实例无关。可以通过类构造函数对象本身访问它们。

        class MyClass {
          static x = 0;
          static printX() {
            console.log(MyClass.x);
          }
        }
        console.log(MyClass.x);
        MyClass.printX();
        

        静态成员也可以使用相同的公共、受保护和私有可见性修饰符,静态成员也会被继承。

        class Base {
          private static x = 0;
          static getGreeting() {
            return "Hello world";
          }
        }
        class Derived extends Base {
          myGreeting = Derived.getGreeting();
        }
        

        name, length, 和call这些特殊的关键词不要定义为静态成员

        类泛型

        类和接口一样,可以是泛型的。当使用 new 实例化一个泛型类时,其类型参数的推断方式与函数调用相同。

        class Box {
          contents: Type;
          constructor(value: Type) {
            this.contents = value;
          }
        }
         
        const b = new Box("hello!");
        

        this

        重要的是要记住,TypeScript 不会改变 JavaScript 的运行时行为,而 JavaScript 因其某些特殊的运行时行为而闻名。

        class MyClass {
          name = "MyClass";
          getName() {
            return this.name;
          }
        }
        const c = new MyClass();
        const obj = {
          name: "obj",
          getName: c.getName,
        };
         
        // Prints "obj", not "MyClass"
        console.log(obj.getName());
        

        js的this是运行时看的,谁调用this就是指的谁。

        上面可以使用箭头函数让this指向类:

        class MyClass {
          name = "MyClass";
          getName = () => {
            return this.name;
          };
        }
        const c = new MyClass();
        const g = c.getName;
        // Prints "MyClass" instead of crashing
        console.log(g());
        

        这样做有一定的代价:

        • 该值保证在运行时是正确的,即使是没有经过 TypeScript 检查的代码也是如此。
        • 这会占用更多内存,因为每个类实例都将拥有以这种方式定义的每个函数的副本
        • 不能在派生类中使用 super.getName,因为在原型链中没有条目可以从基类方法中获取

          或者使用this参数:

          在方法或函数定义中,名为 this 的初始参数在 TypeScript 中具有特殊意义。这些参数在编译时会被删除。

          TypeScript 会检查使用 this 参数调用函数的上下文是否正确。我们可以不使用箭头函数,而是在方法定义中添加 this 参数,静态地强制方法被正确调用。

          class MyClass {
            name = "MyClass";
            getName(this: MyClass) {
              return this.name;
            }
          }
          const c = new MyClass();
          // OK
          c.getName();
           
          // Error, would crash
          const g = c.getName;
          console.log(g());
          

          您可以在类和接口方法的返回位置使用此 Type。当与类型缩小(如 if 语句)混合使用时,目标对象的类型将缩小为指定的类型:

          class FileSystemObject {
            isFile(): this is FileRep {
              return this instanceof FileRep;
            }
            isDirectory(): this is Directory {
              return this instanceof Directory;
            }
            isNetworked(): this is Networked & this {
              return this.networked;
            }
            constructor(public path: string, private networked: boolean) {}
          }
           
          class FileRep extends FileSystemObject {
            constructor(path: string, public content: string) {
              super(path, false);
            }
          }
           
          class Directory extends FileSystemObject {
            children: FileSystemObject[];
          }
           
          interface Networked {
            host: string;
          }
           
          const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
           
          if (fso.isFile()) {
            fso.content;
            
          const fso: FileRep
          } else if (fso.isDirectory()) {
            fso.children;
            
          } else if (fso.isNetworked()) {
            fso.host;
          }
          

          构造参数属性

          TypeScript 提供了特殊的语法,可将构造函数参数转化为具有相同名称和值的类属性。这些属性称为参数属性,通过在构造函数参数前添加 public、private、protected 或 readonly 可见性修饰符来创建。生成的字段将获得这些修饰符:

          class Params {
            constructor(
              public readonly x: number,
              protected y: number,
              private z: number
            ) {
              // No body necessary
            }
          }
          const a = new Params(1, 2, 3);
          console.log(a.x);
          console.log(a.z);
          

          类表达式声明

          类表达式与类声明非常相似。唯一真正的区别是类表达式不需要名称,尽管我们可以通过它们最终绑定的标识符来引用它们:

          const someClass = class {
            content: Type;
            constructor(value: Type) {
              this.content = value;
            }
          };
           
          const m = new someClass("Hello, world");
          

          类实例类型

          JavaScript 类使用 new 运算符进行实例化。鉴于类本身的类型,InstanceType 实用程序类型可以模拟这种操作。

          class Point {
            createdAt: number;
            x: number;
            y: number
            constructor(x: number, y: number) {
              this.createdAt = Date.now()
              this.x = x;
              this.y = y;
            }
          }
          type PointInstance = InstanceType
           
          function moveRight(point: PointInstance) {
            point.x += 5;
          }
           
          const point = new Point(3, 4);
          moveRight(point);
          point.x; // => 8
          

          abstract 修饰符

          TypeScript 中的类、方法和字段可以是抽象的。

          抽象方法或抽象字段是一种尚未提供实现的方法或字段。这些成员必须存在于不能直接实例化的抽象类中。

          抽象类的作用是为实现所有抽象成员的子类提供基类。当一个类没有任何抽象成员时,它就被称为具体类。

          我们不能用 new 来实例化 Base,因为它是抽象的。相反,我们需要创建一个派生类并实现抽象成员:

          abstract class Base {
            abstract getName(): string;
           
            printName() {
              console.log("Hello, " + this.getName());
            }
          }
           // error
          const b = new Base();
          class Derived extends Base {
            getName() {
              return "world";
            }
          }
           
          const d = new Derived();
          d.printName();
          

          类之间

          在大多数情况下,TypeScript 中的类在结构上与其他类型相同。

          例如,这两个类可以相互替代使用,因为它们完全相同:

          class Point1 {
            x = 0;
            y = 0;
          }
           
          class Point2 {
            x = 0;
            y = 0;
          }
           
          // OK
          const p: Point1 = new Point2();
          

          同样,即使没有显式继承,类之间也存在子类型关系:

          class Person {
            name: string;
            age: number;
          }
           
          class Employee {
            name: string;
            age: number;
            salary: number;
          }
           
          // OK
          const p: Person = new Employee();
          

          空类没有成员。**在结构类型系统中,没有成员的类型通常是其他任何类型的超类型。**因此,如果你编写了一个空类(不要!),那么任何东西都可以用来代替它:

          class Empty {}
           
          function fn(x: Empty) {
            // can't do anything with 'x', so I won't
          }
           
          // All OK!
          fn(window);
          fn({});
          fn(fn);
          

          不要这样写,没意义。

          结语

          结束了。

转载请注明来自码农世界,本文标题:《【TypeScript】ts中的Classes使用方法介绍》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,80人围观)参与讨论

还没有评论,来说两句吧...

Top