首页 > 其他分享 >[TS手册学习] 04_类

[TS手册学习] 04_类

时间:2023-12-01 17:44:45浏览次数:49  
标签:const name 04 成员 number TS 访问 手册 class

TS官方手册:TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org)

类 Class

类的成员

初始化

类的成员属性声明类型:

class Point {
  x: number;
  y: number;
}

类的成员属性初始化,会在实例化的时候完成赋值:

class Point {
  x: number = 0;
  y: number = 0;
}
严格初始化

--strictPropertyInitialization配置项为true的时候,要求成员属性必须初始化,否则报错。

可以在声明成员属性的时候初始化,也可以在构造函数中初始化。

class GoodGreeter {
    name: string;
    constructor() {
        this.name = "hello";
    }
}

如果打算在构造函数以外初始化字段,例如依赖一个外部库来填充类的一部分,则可以使用断言运算符!来声明属性是非空的。

class OKGreeter {
  // 没有初始化,但不会报错
  name!: string;
}
只读 readonly

使用readonly修饰,被readonly修饰的成员只能在构造函数中被赋值(初始化),在其它成员方法中的更新操作会导致错误。

class Greeter {
    readonly name: string = "world";

    constructor(otherName?: string) {
        if (otherName !== undefined) {
            this.name = otherName;
        }
    }

    err() {
        // name属性是只读的,这里会导致报错。
        this.name = "not ok";
    }
}
构造函数
  • 参数列表的类型声明;

  • 参数的默认值;

  • 构造函数重载:

    class Point {
        // Overloads
        constructor(x: number, y: string);
        constructor(s: string);
        constructor(xs: any, y?: any) {
            // TBD
        }
    }
    

构造函数签名与函数签名之间的区别:

  • 构造函数不能使用泛型;
  • 构造函数不能声明返回值类型。
成员方法

成员方法可以像函数一样使用类型标注:参数列表的类型与默认值、返回值类型、泛型、重载......

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

:在成员方法中使用成员属性要通过this,否则可能顺着作用域链找到类外部的变量。

let x: number = 0;
class C {
    x: string = "hello";

    m() {
        // 这里的x是第1行的x,类型为number,不能赋值为string,故报错。
        x = "world";
    }
}
访问器 getter/setter

在 JS 中,如果没有需要做数据拦截的需求,是不需要用访问器的,大可以直接将属性public暴露到外部。

在 TS 中,访问器存在如下规则:

  • 如果有getter但没有setter,那么属性是只读的readonly
  • 如果没有指定setter方法的value参数类型,那么则以getter的返回值类型替代;
  • getter和setter的成员可访问性(public/private/protected)必须一致。
索引签名

可以为类的实例定义索引签名,但是很少用,一般将索引数据转移到别处,例如转而使用一个对象类型或者数组类型的成员。

类的继承

和其它面向对象语言一样,JS 中的类可以从基类中继承成员属性和方法。

implements子句(实现接口)
interface Pingable {
    ping(): void;
}
 
class Sonar implements Pingable {
    ping() {
        console.log("ping!");
    }
}

接口只负责声明成员变量和方法,如果一个类要实现一个接口,则需要实现内部的所有方法。

一个类可以实现多个接口。

注意

  1. 如果接口中声明了函数的类型,在实现该接口的类中仍要声明类型:
interface Checkable {
    check(name: string): boolean;
}
 
class NameChecker implements Checkable {
    check(s) {
        // 这里的 s 会被认为是any类型,any类型没有toLowerCase方法,会报错
        return s.toLowerCase() === "ok";
    }
}
  1. 当一个类实现一个接口时,这个接口中的可选属性(optional property)不会被待到类中。
extends子句(继承基类)
class A extends B{}

其中A被称为子类或派生类,B是父类或基类。

继承一个类将继承它的所有成员属性和方法。

方法重写(overriding methods)

可以使用super获取到父类的方法。

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");

可以将一个子类的实例赋值给一个父类的实例(实现多态的基础)。

成员可访问性 member visibility

public

缺省值。使用public修饰的成员可以被任意访问。

protected

只有这个类和它的子类的成员可以访问。

子类在修饰继承自父类的成员可访问性时,最好带上protected,否则会默认地变成public,将成员暴露给外部。

class Base {
  protected m = 10;
}
class Derived extends Base {
  // 没有修饰,默认表示public
  m = 15;
}
const d = new Derived();
console.log(d.m); // 暴露到外部了

跨继承访问protected成员

protected的定义就是只有类本身和子类可以访问。但是在某些面向对象的编程语言中可以通过基类的引用,访问到非本身且非子类的protected成员。

这种操作在 Java 中被允许,但是在C#、C++、TS 中是非法操作。

原则是:如果D2不是D1的子类,根据protected的定义这种访问方式就是不合法的。那么基类跨越这种技巧不能很好的解决问题。当在编码的过程中遇到这种无法访问的权限问题时,应更多地思考类之间的结构设计,而不是采用这种取巧的方式。

image-20231201120318147
class Base {
    protected x: number = 1;
}
class Derived1 extends Base {
    protected x: number = 5;
}
class Derived2 extends Base {
    f1(other: Derived2) {
        other.x = 10;
    }
    f2(other: Derived1) {
        // x被protected修饰,只能被Derived1的子类访问,但是Derived2不是它的子类,无权访问,会报错。
        other.x = 10;
    }
}
private

只有类本身可以访问。

与protected不同,protected在子类中可访问,因此可以在子类中进一步开放可访问性(即改为public)。

但是private修饰的成员无法在子类中访问,因为无法进一步开放可访问性。

跨实例访问private成员

不同的实例只要是由一个类创建,那么它们就可以相互访问各自实例上由private修饰的成员。

class A {
    private x = 10;
    public sameAs(other: A) {
        // 不会报错,因为TS支持跨实例访问private成员
        return other.x === this.x;
    }
}

大多数面向对象语言支持这种特性,例如:JavaC#C++SwiftPHPTS 也支持。Ruby不支持。

注意事项
  • 成员可访问性只在TS的类型检查过程中有效,在最终的 JS 运行时下是无效的,在 JS 运行时下,in操作符和其它获取对象属性的方法可以获取到对象的所有属性,不管在 TS 中它们是public还是protected还是private修饰的。

  • private属性支持使用obj.[key]格式访问,使得单元测试更加方便,但是这种访问方式执行的是不严格的private

    class MySafe {
      private secretKey = 12345;
    }
    const s = new MySafe();
    // 由private修饰的成员无法被访问,这里会报错。
    console.log(s.secretKey);
    // 使用字符串索引访问,不严格,不会报错。
    console.log(s["secretKey"]);
    

静态成员

基本特性

静态成员绑定在类对象上,不需要实例化对象就能访问。

静态成员也可以通过publicprotectedprivate修饰可访问性。

静态成员也可以被继承。

静态成员不能取特殊的变量名,例如:namelengthcall等等。

不要使用Function原型上的属性作为静态成员的变量名,会因为冲突而出错。

静态代码块static block

静态代码块中可以访问到类内部的所有成员和类外部的内容,通常静态代码块用来初始化类。

class Foo {
    static #count = 0;
    get count() {
        return Foo.#count;
    }
    static {
        try {
            const lastInstances = loadLastInstances();
            Foo.#count += lastInstances.length;
        }
        catch {}
    }
}

泛型类

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

泛型类的静态成员不能引用类型参数。

this 在运行时的指向问题

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

// 这里会输出"MyClass"
console.log(c.getName());
// 这里输出结果是"obj",而不是"MyClass",因为方法是通过obj调用的。
console.log(obj.getName());

类的方法内部的this默认指向类的实例。但是一旦将方法挑出外部,单独调用,就很可能报错。因为函数中的this指向调用该函数的对象,成员方法中的this不一定指向它的实例对象,而是指向实际调用它的对象。

一种解决方法:使用箭头函数。

箭头函数中的this指向取决于定义该箭头函数时所处的上下文,而不是调用时。

class MyClass {
    name = "MyClass";
    getName = () => {
        return this.name;
    };
}
const c = new MyClass();
const g = c.getName;
// 这里会输出"MyClass"
console.log(g());

  • 这种解决方案不需要 TS 也能实现;

  • 这种做法会需要更多内存,因为箭头函数不会被放到原型上,每个实例对象都有相互独立的getName方法;

  • 也因为getName方法没有在原型链上,在这个类的子类中,无法使用super.getName访问到getName方法。

另一种解决方法:指定this的类型

我们希望this指向实例对象,意味着this的类型应该是MyClass而不能是其他,通过这种类型声明可以在出错的时候及时发现。

class MyClass {
    name = "MyClass";
    // 指定this必须是MyClass类型
    getName(this: MyClass) {
        return this.name;
    }
}
const c = new MyClass();
// OK
c.getName();

const g = c.getName;
// Error: 这里的this会指向undefined或者全局对象。
console.log(g());

  • 每个类定义分配一个函数,而不是每个类实例分配一个函数;
  • 可以使用super调用,因为存在于原型链上。

this 类型

在类里存在一种特殊的类型this,表示当前类。

返回值类型为this的情况

class Box {
    contents: string = "";
    // set方法返回了this(这里的this是对象的引用),因此set方法的返回值类型被推断为this(这里的this是类型)
    set(value: string) {
        this.contents = value;
        return this;
    }
}

参数类型为this的情况

class Box {
    content: string = "";
    sameAs(other: this) {
        return other.content === this.content;
    }
}

这种情况下的other:thisother:Box不同,当一个类继承自Box时,子类中的sameAs方法的this类型将指向子类类型而不是Box

使用this进行类型守护(type guards)

可以在类或接口的方法的返回值类型处使用this is 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) {}
}
// 这里省略了子类的定义...

// 当需要类型收束时:
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
    // 调用isFile方法将返回boolean类型,并且在这个块内,fso的类型会收束为FileRep
    fso.content;
} else if (fso.isDirectory()) {
    fso.children;
} else if (fso.isNetworked()) {
    fso.host;
}

另外一种常用的情景是:移除undefined类型。

class Box<T> {
    value?: T;
    hasValue(): this is { value: T } {
        return this.value !== undefined;
    }
}
 
const box = new Box();
box.value = "Gameboy";

// (property) Box<unknown>.value?: unknown
box.value;
 
if (box.hasValue()) {
    // (property) value: unknown
    box.value;
}

参数属性

由于构造函数的参数列表和成员属性的属性名大多数时候都是一致的:

class Box{
    private width: number = 0;
    private height: number = 0;
    constructor(width: number, height:number){
        this.width = width;
        this.height = height;
    }
}

TS 支持给类构造函数的参数添加修饰,例如publicprotectedprivatereadonly。只需要在参数列表添加修饰就完成初始化操作,不需要写构造函数的函数体:

class Params {
    constructor(
    	public readonly x: number,
     	protected y: number,
     	private z: number
    ) {
        // 不需要函数体
    }
}
const a = new Params(1, 2, 3);
console.log(a.x); // 1
console.log(a.z); // Error: z是私有属性,无法访问

类表达式

类表达式和类的声明十分相似,类表达式可以是匿名的,也可以将其赋值给任意标识符并引用它。

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

类表达式实际上是 JS 就有的语法,TS 只是提供了类型标注、泛型等额外的特性。

获取实例类型

使用InstanceType

class Point {
    createdAt: number;
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.createdAt = Date.now();
        this.x = x;
        this.y = y;
    }
}
// 获取Point这个类的实例类型
type PointInstance = InstanceType<typeof Point>
 
function moveRight(point: PointInstance) {
    point.x += 5;
}
 
const point = new Point(3, 4);
moveRight(point);
point.x; // => 8

似乎这里可以直接用point:Point替代point:PointInstance,但是在其它没有使用class(语法糖)的场景下,InstanceType有以下作用:

image-20231201171526978

标签:const,name,04,成员,number,TS,访问,手册,class
From: https://www.cnblogs.com/feixianxing/p/typescript-handbook-class-member-method-property-con

相关文章

  • Pinpoint 04
    剪贴簿Ⅰ量力而少行,欲速则不达◆Don'tdomoretodaythanyoucancompletelyrecoverfrombytomorrow提问:首次到达南极点的科考队属于哪个国家?A.挪威B.英国[英字|MattD'Avella]那些自律博主没有告诉你的关于效率的事情这是一个颇具寓言意义的历史事件。彼时,从......
  • RTSP安防系统LiteCVR平台接入RTSP设备出现离线情况的排查
    随着科技的飞速进步,视频监控系统已经成为了我们生活和工作中不可或缺的一部分。从最早的模拟监控系统到现在的数字监控系统,视频监控经历了漫长的发展历程。如今,我们生活在一个被视频监控系统包围的时代,无论是城市交通、银行、商场、公共场所等,都离不开视频监控系统的应用。有用户......
  • 2024年几种浏览器播放RTSP视频流的方案及优缺点
    现在越来越多的场景需要用到视频设备了,比如:交通部门、停车场、园区等,大部分需要在浏览器中实时播放视频流,因为RTSP协议具有可扩展性、安全性和易解析等特点,成为大部分摄像头厂商的首选协议,众所周知的是RTSP协议的视频流,浏览器中是无法直接使播放,只有通过插件或者转码来实现。市场......
  • vscode插件 runcode 无法运行ts
    declarefunctionpick<TextendsRecord<string,unknown>>(traget:Record<string,unknown>,...keys:(keyofT)[]):unknown;//pick({asdfasfsa:'123'},'a','1').asdfasfsaconstsource={name:'John',......
  • 一键生成requirements.txt
    pipfreeze>requirements.txt想把requirements.txt放在哪里就在编译器中进入那个地址例如我想放在根目录下(目前来说requirements.txt都是放在根目录下)   回车后一键生成所有项目中的依赖,别人后续在对你的项目进行操作时,一键安装依赖一键安装命令pipinstall-rrequi......
  • JFinal启动成功之后,使用localhost访问浏览器界面,显示404(之前是可以滴~)
    问题描述问题解决应该是我没有设置只输入localhost弹出的浏览器的html页面内容;然后我只需要调用到localhost/student/,就显示出来相应的界面啦~~~说白了就是路径问题,,......
  • HighCharts 极地图图表绘制及添加标示线+柱状图找最值
    需求:绘制极地图并给极地图图表加上标示线,在柱状图中找出最值分析:图表加上标示线在需要的轴上面用plotLines(标示线)属性来进行添加,极地图则是在chart(图表)属性里开启polar(极)属性然后进行绘制,找出最值需要在plotOptions属性里进行修改解决:源代码:示例:标示线总是垂直于它属于的轴。......
  • ORA-01187 cannot read from file 201 because it failed verification tests..temp01
    Description:WegetthismessageinrunningtheUpgradeExpress20-21export(create_customer_data):ORA-01187:cannotreadfromfile201becauseitfailedverificationtestsORA-01110:datafile201:'/exlibris/oradata/aleph20/aleph20_temp01.dbf'O......
  • 【直播协议详解】RTMP、HLS、HTTP-FLV、WebRTC、RTSP的区别
    本期我们详细讨论直播的相关协议,包括:HTTP-FLV、HLS、RTMP、Web-RTC、RTSP等等。我们将会详细介绍这些协议的工作原理、应用场景、及延迟的原因。我们按这样的顺序讨论​:RTMP、HTTP-FLVHLSWeb-RTCRTSP一、RTMP、HTTP-FLV协议RTMP和HTTP-FLV都是建立在FLV封装之上的。RTM......
  • Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析
    最近DOTS发布了正式的版本,我们来分享一下DOTS里面Baking核心机制,方便大家上手学习掌握UnityDOTS开发。今天给大家分享的Baking机制中的FilterBakingOutput与PrefabInBaking。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发......