[TS手册学习] 04_类

这篇具有很好参考价值的文章主要介绍了[TS手册学习] 04_类。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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的定义这种访问方式就是不合法的。那么基类跨越这种技巧不能很好的解决问题。当在编码的过程中遇到这种无法访问的权限问题时,应更多地思考类之间的结构设计,而不是采用这种取巧的方式。

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有以下作用:

👉引用自javascript - typescript的InstanceType怎么用呀? - SegmentFault 思否

抽象类与成员

TS 中的类和成员可以是抽象的。

抽象类与成员的特点:

  • 抽象方法或属性是指尚未提供实现的方法或属性。
  • 这些抽象成员必须存在于抽象类中。
  • 抽象类不能直接实例化。

抽象类必须充当基类,让派生类去实现抽象成员。

当一个类不存在任何抽象成员的时候,就说它是具体类。

abstract class Base {
    abstract getName(): string;

    printName() {
        console.log("Hello, " + this.getName());
    }
}

// 继承
class Derived extends Base {
    // 实现基类的抽象成员
    getName() {
        return "world";
    }
}

如果没有实现基类的抽象成员,则会报错。

类之间的关系

TS 通过类的结构区分不同的类,如果两个类的结构一致,则认为是同一个类。

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

只要一个类的字段集合是另一个类的字段集合的子集,就可以实现类似于继承的效果(不需要使用extends关键字)。

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) {
	// x对象没有字段,这里做不了任何操作
}
 
// 但是下面这些对象的字段集合都包含空集,也就是说都不会报错。
fn(window);
fn({});
fn(fn);

永远不要使用空的类。文章来源地址https://www.toymoban.com/news/detail-747671.html

到了这里,关于[TS手册学习] 04_类的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 04.阿里Java开发手册——注释规约

    【强制】 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /**内容*/ 格式,不得使用 // xxx 方式。 说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义

    2024年01月16日
    浏览(43)
  • ts 终于搞懂TS中的泛型啦! | typescript 入门指南 04

    大家好,我是王天~ 这篇文章是 ts入门指南系列中第四篇,主要讲解ts中的泛型应用,泛型在ts中是比较重要的概念,我花挺长时间才搞明白的,希望能帮助到大家 ~ ** ts 入门指南系列 ** Ts和Js 谁更适合前端开发?| typescript 入门指南 01 详解tsconfig.json 配置文件 | 02 ts入门指南

    2024年02月08日
    浏览(42)
  • 20230720在ubuntu22.04系统下载+解密+合并ts切片的步骤

    20230720在ubuntu22.04系统下载+解密+合并ts切片的步骤 2023/7/20 23:06 1、视频源头,打开时效肯定有时间限制的! 【并且不同时间打开,下载链接/参数会有区别的!以前的链接就会失效/出错了!】 https://app1ce7glfm1187.h5.xiaoeknow.com/v2/course/alive/l_64af6130e4b03e4b54da1681?type=2app_id=app1cE7gLFM

    2024年02月15日
    浏览(56)
  • 【ELK04】ES 分词计算、IK分词器安装使用手册和热词动态更新

    本小结主要了解的内容是: 了解分词器的概念 掌握IK分词器和热词配置 ES中为了方便查询,提供多维度的查询功能,对存储在索引中的文档进行分词计算,但是文本内容不同,类型不同,语言不同分词计算逻辑就不会一样. 文本分析使Elasticsearch能够执行全文搜索,其中搜索返回所有

    2024年02月04日
    浏览(37)
  • 银河麒麟高级服务器操作系统V10-系统管理员手册:04 安装和管理软件

    目录 第四章 安装和管理软件 4.1. 检查和升级软件包 4.1.1. 软件包升级检查 4.1.2. 升级软件包 4.1.3. 利用系统光盘与 dnf 离线升级系统 4.2. 管理软件包 4.2.1. 检索软件包 4.2.2. 安装包列表 4.2.3. 显示软件包信息 4.2.4. 安装软件包 4.2.5. 下载软件包 4.2.6. 删除软件包 4.3. 管理软件包组

    2024年02月03日
    浏览(58)
  • 网络安全(黑客)学习手册

    网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 一是市场需求量高; 二则是发展相对成熟入门比较容易。 需要掌握的知识点偏多, 外围打点能力 钓鱼远

    2024年02月15日
    浏览(46)
  • AIGC学习手册

    在经过大量实验后得出一些经验 如果没有足够的审美和设计功底来驾驭AI,那它只是一个壁纸连连看生成器。 Al未来应该会细分为很多方向,但稳定可控、可预见效果的Al才能真正的不再局限,加入工作流之中。 对参数和数据敏感的设计师会更容易上手。也就是除了美术功底

    2024年01月20日
    浏览(32)
  • node学习手册

    Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,使 JavaScript 可以脱离浏览器环境运行在服务端。它提供了一组 API,可以让开发者轻松地进行服务器端编程。 以下是 Node.js 的学习手册: 首先,需要在官网上下载 Node.js 的安装包,然后按照安装向导进行安装。安装完成后,可

    2024年02月04日
    浏览(22)
  • SQL 注入学习手册【笔记】

    基本步骤 1. 判断注入类型 数字型 or 字符型 数字型【示例】: ?id=1 字符型【示例】: ?id=1\\\' 这也是在尝试闭合原来的 sql 语句,用包括 \\\" \\\' ) 不限于这些字符尝试闭合,同时也可以根据它的语句报错来推断闭合符 还有一种判断闭合符方法【用 号】 ?id=1 根据语句报错返回的信

    2024年02月13日
    浏览(40)
  • 接口测试学习手册

    很多人会谈论接口测试。到底什么是接口测试?如何进行接口测试?这篇文章会帮到你。 在谈论接口测试之前,让我们先明确前端和后端这两个概念。 前端是我们在网页或移动应用程序中看到的页面,它由 HTML 和 CSS 编写而成,让我们看到漂亮的页面,并进行一些简单的校验

    2024年02月06日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包