鸿蒙harmony--TypeScript类详解

news/2024/7/17 3:34:35

 今天是正月初三,许下三个心愿,一愿家人安康,亲人在旁,二愿山河无恙,人间皆安,三愿喜乐无忧,生活明朗,愿你好事接二连三,新年福气相伴!

目录

一,类成员

1.1 字段

1.2 readonly

1.3 构造器

超类调用

1.4 方法

1.5 获取器/设置器

1.6 索引签名

二,类继承

2.1 implements

注意事项

2.2 extends

覆盖方法

仅类型字段声明

初始化顺序

三,成员可见性

3.1 public

3.2 protected

导出 protected 成员

跨层级 protected 访问

3.3 private

四,静态成员

4.1 特殊静态名称

4.2 为什么没有静态类

五,static 类中的块

六,泛型类

静态成员中的类型参数

七,类运行时的 this

箭头函数

八,this 类型

this 型防护

九,abstract 类和成员

一,类成员

这是最基本的类 - 一个空的:

class YuanZhen{}

1.1 字段

字段声明在类上创建公共可写属性:

class YuanZhen{name:stringage:number
}const yuanZhen = new YuanZhen();
yuanZhen.name="袁震"
yuanZhen.age=30

与其他位置一样,类型注释是可选的,但如果未指定,则为隐式 any

字段也可以有初始化器;这些将在实例化类时自动运行:

如果你打算通过构造函数以外的方式明确地初始化一个字段(例如,可能一个外部库正在为你填充你的类的一部分),你可以使用明确的赋值断言运算符,!

class YuanZhen{name!:stringage:number
}

1.2 readonly

字段可以以 readonly 修饰符作为前缀。这可以防止对构造函数之外的字段进行赋值。

class Greeter {readonly name: string = "world";constructor(otherName?: string) {if (otherName !== undefined) {this.name = otherName;}}err() {this.name = "not ok";
//Cannot assign to 'name' because it is a read-only property.}
}
const g = new Greeter();
g.name = "also not ok";
//Cannot assign to 'name' because it is a read-only property.

1.3 构造器

类构造函数与函数非常相似。你可以添加带有类型注释、默认值和重载的参数:

class YuanZhen{name!:stringage:numberconstructor(name = "袁震", age = 20) {this.name = namethis.age = age}}
class YuanZhen{name!:stringage:numberconstructor(name:string, age :number)constructor(name :string)constructor() {}
}

类构造函数签名和函数签名之间只有一些区别:

1,构造函数不能有类型参数 - 这些属于外部类声明

2,构造函数不能有返回类型注释 - 类实例类型始终是返回的内容

超类调用

就像在 JavaScript 中一样,如果你有一个基类,在使用任何 this. 成员之前,你需要在构造函数主体中调用 super();

class YuanZhen{name!:stringage:numberconstructor(name:string, age :number)constructor(name :string)constructor() {}
}class Yz extends YuanZhen{constructor(name:string, age :number){//Constructors for derived classes must contain a 'super' call. <ArkTSCheck>// super(name,age)}
}

1.4 方法

类上的函数属性称为方法。方法可以使用所有与函数和构造函数相同的类型注释:

class YuanZhen{name!:stringage:numberconstructor(name:string, age :number)constructor(name :string)constructor() {}work(){console.info("name="+this.name+"age="+this.age)}
}

请注意,在方法体内,仍然必须通过 this. 访问字段和其他方法。方法主体中的非限定名称将始终引用封闭作用域内的某些内容

1.5 获取器/设置器

类也可以有访问器:

class YuanZhen{name!:stringage:numberconstructor(name:string, age :number)constructor(name :string)constructor() {}work(){console.info("name="+this.name+"age="+this.age)}getName():string{return this.name}setName(name:string){this.name=name}
}

注意,没有额外逻辑的由字段支持的 get/set 对在 JavaScript 中很少有用。如果你不需要在 get/set 操作期间添加其他逻辑,则可以公开公共字段。

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

 1.如果 get 存在但没有 set,则属性自动为 readonly

 2.如果不指定 setter 参数的类型,则从 getter 的返回类型推断

3.getter 和 setter 必须有相同的 成员可见性

1.6 索引签名

class MyClass {[s: string]: boolean | ((s: string) => boolean);check(s: string) {return this[s] as boolean;}
}

因为索引签名类型还需要捕获方法的类型,所以要有效地使用这些类型并不容易。通常最好将索引数据存储在另一个地方而不是类实例本身。

二,类继承

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

2.1 implements

可以使用 implements 子句来检查一个类是否满足特定的 interface。如果一个类未能正确实现它,则会触发错误:

interface Pingable {ping(): void;
}class Sonar implements Pingable {ping() {console.log("ping!");}
}class Ball implements Pingable {
//Class 'Ball' incorrectly implements interface 'Pingable'.
//  Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.pong() {console.log("pong!");}
}

类也可以实现多个接口,例如 class C implements A, B 

注意事项

重要的是要理解 implements 子句只是检查类可以被视为接口类型。它根本不会改变类的类型或其方法。常见的错误来源是假设 implements 子句将更改类类型 - 事实并非如此!

interface Checkable {check(name: string): boolean;
}class NameChecker implements Checkable {check(s) {
//Parameter 's' implicitly has an 'any' type.// Notice no error herereturn s.toLowerCase() === "ok";}
}

在这个例子中,我们可能预计 s 的类型会受到 check 的 name: string 参数的影响。它不是 - implements 子句不会更改类主体的检查方式或其类型推断方式。

同样,使用可选属性实现接口不会创建该属性:

interface A {x: number;y?: number;
}
class C implements A {x = 0;
}
const c = new C();
c.y = 10;
//Property 'y' does not exist on type 'C'.

2.2 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. 语法来访问基类方法。请注意,因为 JavaScript 类是一个简单的查找对象,所以没有 “超级字段” 的概念。

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

如果 Derived 不遵守 Base 的合同怎么办?

class Base {greet() {console.log("Hello, world!");}
}class Derived extends Base {// Make this parameter requiredgreet(name: string) {
//Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'.
//  Type '(name: string) => void' is not assignable to type '() => void'.console.log(`Hello, ${name.toUpperCase()}`);}
}

如果我们在出现错误的情况下编译此代码,则此示例将崩溃:

const b: Base = new Derived();
// Crashes because "name" will be undefined
b.greet();
仅类型字段声明

父类构造函数完成后初始化类字段,覆盖父类设置的任何值。当你只想为继承的字段重新声明更准确的类型时,这可能会成为问题。为了处理这些情况,你可以写 declare 来向 TypeScript 表明这个字段声明不应该有运行时影响。

interface Animal {dateOfBirth: any;
}interface Dog extends Animal {breed: any;
}class AnimalHouse {resident: Animal;constructor(animal: Animal) {this.resident = animal;}
}class DogHouse extends AnimalHouse {// Does not emit JavaScript code,// only ensures the types are correctdeclare resident: Dog;constructor(dog: Dog) {super(dog);}
}
初始化顺序
class Base {name = "base";constructor() {console.log("My name is " + this.name);}
}class Derived extends Base {name = "derived";
}// Prints "base", not "derived"
const d = new Derived();

这里发生了什么?

1.基类字段被初始化

2.基类构造函数运行

3.派生类字段被初始化

4.派生类构造函数运行

这意味着基类构造函数在其自己的构造函数中看到了自己的 name 值,因为派生类字段初始化尚未运行。

三,成员可见性

3.1 public

类成员的默认可见性为 publicpublic 成员可以在任何地方访问:

class Greeter {public greet() {console.log("hi!");}
}
const g = new Greeter();
g.greet();

因为 public 已经是默认的可见性修饰符,所以你不需要在类成员上编写它,但出于样式/可读性的原因可能会选择这样做。

3.2 protected

protected 成员仅对声明它们的类的子类可见。

class Greeter {public greet() {console.log("Hello, " + this.getName());}protected getName() {return "hi";}
}class SpecialGreeter extends Greeter {public howdy() {// OK to access protected member hereconsole.log("Howdy, " + this.getName());}
}
const g = new SpecialGreeter();
g.greet(); // OK
g.getName();
//Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.
导出 protected 成员

派生类需要遵循其基类契约,但可以选择公开具有更多功能的基类子类型。这包括让 protected 成员成为 public

class Base {protected m = 10;
}
class Derived extends Base {// No modifier, so default is 'public'm = 15;
}
const d = new Derived();
console.log(d.m); // OK

请注意,Derived 已经能够自由读写 m,因此这并没有有意义地改变这种情况下的 “security”。这里需要注意的主要一点是,在派生类中,如果这种暴露不是故意的,我们需要小心重复 protected 修饰符。

跨层级 protected 访问

不同的 OOP 语言对于通过基类引用访问 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) {other.x = 10;
//Property 'x' is protected and only accessible within class 'Derived1' and its subclasses.}
}

例如,Java 认为这是合法的。另一方面,C# 和 C++ 选择此代码应该是非法的。

TypeScript 在这里支持 C# 和 C++,因为在 Derived2 中访问 x 应该只在 Derived2 的子类中是合法的,而 Derived1 不是其中之一。此外,如果通过 Derived1 引用访问 x 是非法的(当然应该如此!),那么通过基类引用访问它永远不会改善这种情况。

3.3 private

private 类似于 protected,但不允许从子类访问成员:

class Base {private x = 0;
}
const b = new Base();
// Can't access from outside the class
console.log(b.x);
//Property 'x' is private and only accessible within class 'Base'.
class Derived extends Base {showX() {// Can't access in subclassesconsole.log(this.x);
//Property 'x' is private and only accessible within class 'Base'.}
}

因为 private 成员对派生类不可见,所以派生类不能增加它们的可见性:

class Base {private x = 0;
}
class Derived extends Base {
//Class 'Derived' incorrectly extends base class 'Base'.
//  Property 'x' is private in type 'Base' but not in type 'Derived'.x = 1;
}

四,静态成员

 类可能有 static 成员。这些成员不与类的特定实例相关联。它们可以通过类构造函数对象本身访问:

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

静态成员也可以使用相同的 publicprotected 和 private 可见性修饰符:

class MyClass {private static x = 0;
}
console.log(MyClass.x);
//Property 'x' is private and only accessible within class 'MyClass'.

静态成员也被继承:

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

4.1 特殊静态名称

从 Function 原型覆盖属性通常是不安全/不可能的。因为类本身就是可以用 new 调用的函数,所以不能使用某些 static 名称。namelength 和 call 等函数属性无法定义为 static 成员:

class S {static name = "S!";
//Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.
}

4.2 为什么没有静态类

TypeScript(和 JavaScript)没有一个名为 static class 的构造,就像 C# 一样。

这些构造之所以存在,是因为这些语言强制所有数据和函数都在一个类中;因为 TypeScript 中不存在该限制,所以不需要它们。只有一个实例的类通常只表示为 JavaScript/TypeScript 中的普通对象。

例如,我们不需要 TypeScript 中的 “静态类” 语法,因为常规对象(甚至顶层函数)也可以完成这项工作:

// Unnecessary "static" class
class MyStaticClass {static doSomething() {}
}// Preferred (alternative 1)
function doSomething() {}// Preferred (alternative 2)
const MyHelperObject = {dosomething() {},
};

五,static 类中的块

静态块允许你编写具有自己作用域的语句序列,这些语句可以访问包含类中的私有字段。这意味着我们可以编写具有编写语句的所有功能的初始化代码,不会泄漏变量,并且可以完全访问我们类的内部结构。

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

六,泛型类

类,很像接口,可以是泛型的。当使用 new 实例化泛型类时,其类型参数的推断方式与函数调用中的方式相同:

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

类可以像接口一样使用泛型约束和默认值。

静态成员中的类型参数

此代码不合法​​,原因可能并不明显:

class Box<Type> {static defaultValue: Type;
//Static members cannot reference class type parameters.
}

请记住,类型总是被完全擦除!在运行时,只有一个 Box.defaultValue 属性槽。这意味着设置 Box<string>.defaultValue(如果可能的话)也会更改 Box<number>.defaultValue - 不好。泛型类的 static 成员永远不能引用类的类型参数。

七,类运行时的 this

TypeScript 不会改变 JavaScript 的运行时行为,并且 JavaScript 以具有一些特殊的运行时行为而闻名。

箭头函数

如果你有一个经常以丢失其 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());

这有一些权衡:

1.this 值保证在运行时是正确的,即使对于未使用 TypeScript 检查的代码也是如此

2.这将使用更多内存,因为每个类实例都会有自己的每个以这种方式定义的函数的副本

3.你不能在派生类中使用 super.getName,因为原型链中没有条目可以从中获取基类方法

八,this 类型

在类中,一种称为 this 的特殊类型动态地引用当前类的类型。让我们看看这有什么用处:

class Box {contents: string = "";set(value: string) {(method) Box.set(value: string): thisthis.contents = value;return this;}}

在这里,TypeScript 推断 set 的返回类型为 this,而不是 Box。现在让我们创建一个 Box 的子类:

class ClearableBox extends Box {clear() {this.contents = "";}
}const a = new ClearableBox();
const b = a.set("hello");
//const b: ClearableBox

你还可以在参数类型注释中使用 this

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

这与编写 other: Box 不同 - 如果你有一个派生类,它的 sameAs 方法现在将只接受同一个派生类的其他实例:

class Box {content: string = "";sameAs(other: this) {return other.content === this.content;}
}class DerivedBox extends Box {otherContent: string = "?";
}const base = new Box();
const derived = new DerivedBox();
derived.sameAs(base);
//Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'.
//  Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.

this 型防护

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

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;//const fso: Directory
} else if (fso.isNetworked()) {fso.host;//const fso: Networked & FileSystemObject
}

基于 this 的类型保护的一个常见用例是允许对特定字段进行延迟验证。例如,当 hasValue 被验证为真时,这种情况会从框中保存的值中删除 undefined

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

九,abstract 类和成员

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

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

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

abstract class Base {abstract getName(): string;printName() {console.log("Hello, " + this.getName());}
}const b = new Base();
//Cannot create an instance of an abstract class.

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

class Derived extends Base {getName() {return "world";}
}const d = new Derived();
d.printName();

请注意,如果我们忘记实现基类的抽象成员,我们会得到一个错误:

class Derived extends Base {
//Non-abstract class 'Derived' does not implement inherited abstract member 'getName' from class 'Base'.// forgot to do anything
}


https://www.xjx100.cn/news/3270633.html

相关文章

2024-02-08 Unity 编辑器开发之编辑器拓展1 —— 自定义菜单栏

文章目录 1 特殊文件夹 Editor2 在 Unity 菜单栏中添加自定义页签3 在 Hierarchy 窗口中添加自定义页签4 在 Project 窗口中添加自定义页签5 在菜单栏的 Component 菜单添加脚本6 在 Inspector 为脚本右键添加菜单7 加入快捷键8 小结 1 特殊文件夹 Editor ​ Editor 文件夹是 …

读千脑智能笔记10_人类智能存在的风险

1. 人类智能存在的风险 1.1. “末日时钟” 1.1.1. 核战争引发的大火列为地球毁灭的主要原因 1.1.2. 气候变化列为人类自我毁灭的第二大潜在原因 1.2. 除非我们刻意加入自私的驱动力、动机或情感&#xff0c;否则智能机器并不会威胁到人类的生存 1.2.1. 人类在不远的将来会…

【原创 附源码】Flutter安卓及iOS海外登录--Google登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月8日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&#…

可视化地图十大样式,别说没告诉你。

地图在可视化界面设计应用十分广泛&#xff0c;一般也会占据界面的C位置&#xff0c;是可视化设计的灵魂所在&#xff0c;本文带来地图可视化设计的十大样式。 1.3D贴图地图 图片来自网络 2.飞溅地图 图片来自网络 3.烟花地图 图片来自网络 4.蝌蚪线地图 图片来自网络 5.热…

【MySQL】-18 MySQL综合-4(MySQL储存引擎精讲+MySQL数据类型简介+MySQL整数类型+MySQL小数类型)

MySQL储存引擎精讲MySQL数据类型简介MySQL整数类型MySQL小数类型 十一 MySQL存储引擎精讲11.1 什么是存储引擎11.2 MySQL 5.7 支持的存储引擎11.3 如何选择 MySQL 存储引擎11.4 MySQL 默认存储引擎 十二 MySQL数据类型简介12.1 MySQL 常见数据类型1) 整数类型2) 日期/时间类型3…

汉服租赁网站:Java技术的文化应用

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

elasticsearch增删改查

一、数据类型 1、字符串类型 &#xff08;1&#xff09;text &#xff08;2&#xff09;keyword 2、数值类型 &#xff08;1&#xff09;long、integer、short、byte、float、double 3、日期类型 &#xff08;1&#xff09;date 4、布尔类型 &#xff08;1&#xff0…

【回溯】37. 解数独

37. 解数独 解题思路 通过solveSudoku方法开始求解数独问题&#xff0c;然后调用backtrack方法进行递归回溯搜索。backtrack方法通过两个参数i和j来确定当前搜索的位置。 在backtrack方法中&#xff0c;首先检查当前位置是否超出了数独的边界&#xff0c;若超出&#xff0c;则…