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

简言

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

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

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

Classes

类声明

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

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

typescript 复制代码
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修饰,这样在构造函数之外无法赋值。

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

类方法

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

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

typescript 复制代码
class Point {
  x = 10;
  y = 10;
 
  scale(n: number): void {
    this.x *= n;
    this.y *= n;
  }
}

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

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

  • 如果存在 get 但没有 set,则属性自动为只读属性。
  • 如果未指定设置器(setters)参数的类型,则根据获取器(getters)的返回类型进行推断。
  • 获取器和设置器必须具有相同的成员可见性。
typescript 复制代码
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 子句来检查类是否满足特定接口。如果类未能正确实现该接口,则会出错:

typescript 复制代码
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 子句只是检查类是否可以被视为接口类型。它根本不会改变类或其方法的类型。

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

  }
}

继承 extends

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

typescript 复制代码
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 强制规定派生类始终是其基类的子类型。

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

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

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

静态成员

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

typescript 复制代码
class MyClass {
  static x = 0;
  static printX() {
    console.log(MyClass.x);
  }
}
console.log(MyClass.x);
MyClass.printX();

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

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

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

类泛型

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

typescript 复制代码
class Box<Type> {
  contents: Type;
  constructor(value: Type) {
    this.contents = value;
  }
}
 
const b = new Box("hello!");

this

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

typescript 复制代码
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指向类:

typescript 复制代码
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 参数,静态地强制方法被正确调用。
typescript 复制代码
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 语句)混合使用时,目标对象的类型将缩小为指定的类型:

typescript 复制代码
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 可见性修饰符来创建。生成的字段将获得这些修饰符:

typescript 复制代码
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);

类表达式声明

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

typescript 复制代码
const someClass = class<Type> {
  content: Type;
  constructor(value: Type) {
    this.content = value;
  }
};
 
const m = new someClass("Hello, world");

类实例类型

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

typescript 复制代码
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<typeof Point>
 
function moveRight(point: PointInstance) {
  point.x += 5;
}
 
const point = new Point(3, 4);
moveRight(point);
point.x; // => 8

abstract 修饰符

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

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

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

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

typescript 复制代码
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 中的类在结构上与其他类型相同。

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

typescript 复制代码
class Point1 {
  x = 0;
  y = 0;
}
 
class Point2 {
  x = 0;
  y = 0;
}
 
// OK
const p: Point1 = new Point2();

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

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

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

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

不要这样写,没意义。

结语

结束了。

相关推荐
梓仁沐白24 分钟前
ubuntu+windows双系统切换后蓝牙设备无法连接
windows·ubuntu
学不会•32 分钟前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS1 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜3 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点3 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow3 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o3 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
刚刚好ā4 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
yqcoder5 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
会发光的猪。6 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js