当前位置:网站首页>[TypeScript] Deep Learning of TypeScript Classes (Part 1)
[TypeScript] Deep Learning of TypeScript Classes (Part 1)
2022-08-03 00:34:00 【Grill the ai on the ocean floor】
TypeScript学习:TypeScript从入门到精通
Analysis of the real questions of the Blue Bridge Cup National Championship:蓝桥杯Web国赛真题解析
Analysis of the real questions of the Blue Bridge provincial competition:蓝桥杯Web省赛真题解析
个人简介:即将大三的学生,热爱前端,热爱生活
你的一键三连是我更新的最大动力️!
文章目录
前言
最近博主一直在创作
TypeScript
的内容,所有的TypeScript
文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅️
本篇文章将深入去讲解TypeScript
中的class
类(由于内容较多,Divide it into two parts),这也许会是你看过的最全面最细致的TypeScript
教程,点赞关注收藏不迷路!
1、类成员
类属性
在一个类上声明字段,创建一个公共的可写属性:
class Point {
x: number;
y: number;
}
const pt = new Point();
pt.x = 0;
pt.y = 0;
表示在类Point
The above declares the type as number
的两个属性,In this case, the compiler may report an error:
这由tsconfig.json
下的strictPropertyInitialization
字段控制:
strictPropertyInitialization
Controls whether class fields need to be initialized in the constructor- 将其设为
false
This error message can be turned off,But this is not recommended
We should explicitly set initializers on properties when declaring them,这些初始化器将在类被实例化时自动运行:
class Point {
x: number = 0;
y: number = 0;
}
const pt = new Point();
// Prints 0, 0
console.log(`${
pt.x}, ${
pt.y}`);
或:
class Point {
x: number;
y: number;
constructor() {
this.x = 0;
this.y = 0;
}
}
const pt = new Point();
// Prints 0, 0
console.log(`${
pt.x}, ${
pt.y}`);
Type annotations in classes are optional,如果不指定,will be an implicitany
类型,但TypeScript
Its type is inferred from its initialization value:
If you intend to initialize a field by means other than the constructor,为了避免报错,你可以使用以下方法:
- Deterministic assignment assertion operator
!
- 使用可选属性
?
- Explicitly add undefined properties(Same principle as optional attributes)
class Point {
// 没有初始化,但没报错
x!: number; // 赋值断言!
y?: number; // 可选属性?
z: number | undefined; // Add undefined type
}
const pt = new Point();
console.log(pt.x, pt.y, pt.z); // undefined undefined undefined
pt.x = 1;
pt.y = 2;
pt.z = 3;
console.log(pt.x, pt.y, pt.z); // 1 2 3
readonly
readonly
修饰符,修饰只读属性,Can prevent assignment of fields outside the constructor:
设置readonly
properties can only be used in initializer expressions or constructor
Modify assignments in ,Even the methods in the class(如chg
)都不行
构造器
类中的构造函数constructor
与函数相似,Parameters with type annotations can be added,默认值和重载:
class Point {
x: number;
y: number;
// Normal signature with type annotation and default value
constructor(x: number = 1, y: number = 2) {
this.x = x;
this.y = y;
}
}
class Point {
x: number;
y: number;
// 重载
constructor(x: number);
constructor(x: number, y: number);
constructor(x: number = 1, y: number = 2) {
this.x = x;
this.y = y;
}
}
类的构造函数签名和函数签名之间只有一些区别:
- 构造函数不能有类型参数(泛型参数)
- 构造函数不能有返回类型注释——What is returned is always the instance type of the class
Super调用
就像在JavaScript
中一样,如果你有一个基类,在使用任何 this.成员
之前,你需要在构造器主体中调用super()
:
class Base {
k = 4;
}
class Derived extends Base {
constructor() {
super();
console.log(this.k);
}
}
方法
类上的函数属性称为方法,All of the same type annotations as functions and constructors can be used:
class Point {
x = 10;
y = 10;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
除了标准的类型注解,TypeScript
并没有为方法添加其他新的东西.
注意: 在一个方法体中,仍然必须通过this
访问字段和其他方法.方法体中的非限定名称将总是指代包围范围内的东西:
let x: number = 0;
class Point {
x = 10;
scale(n: number): void {
x *= n; // This is modifying the first linex变量,不是类属性
}
}
Getters/Setters
使用Getters/Setters
的规范写法:
class Point {
_x = 0;
get x() {
console.log("get");
return this._x;
}
set x(value: number) {
console.log("set");
this._x = value;
}
}
let a = new Point();
// 调用了set
a.x = 8; // set
// 调用了get
console.log(a.x); // get 8
Naming convention here:
- 以
_
Defined at the beginning is used forget/set
的属性(Different from ordinary properties):_x
- 用
get
和set
The prefixes are defined separatelyget/set
函数,The function names are the samex
,Indicates that these two belong_x
的get/set
函数 - Direct when accessing and modifying
.x
触发get/set
,而不是._x
- This way it can be used like a normal property,如上
a.x = 8
和console.log(a.x)
TypeScript
对访问器有一些特殊的推理规则:
如果存在
get
,但没有set
,则该属性自动是只读的如果没有指定
setter
参数的类型,它将从getter
的返回类型中推断出来访问器和设置器必须有相同的成员可见性(Member visibility is discussed below)
从TypeScript 4.3
开始,There can be different types访问器用于获取和设置:
class Thing {
_size = 0;
get size(): number {
return this._size;
}
set size(value: string | number | boolean) {
// There can be different types
let num = Number(value);
// 不允许NaN、Infinity等
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
索引签名
Classes can also use index signatures like other object types,Their index signatures do the same thing:
class MyClass {
[s: string]: boolean | ((s: string) => boolean);
check(s: string) {
return this[s] as boolean;
}
}
Because the index signature type is requiredAlso capture the type of the method(That's why the above index types are required|((s: string) => boolean)
,Its purpose is to be compatiblecheck
方法),所以要有用地使用这些类型并不容易
一般来说,最好将索引数据存储在另一个地方,而不是在类实例本身
2、类继承
implements子句
implements
Clauses can make classes实现一个接口(Make the type of the class conform to this interface),Then use it to check whether a class satisfies a specific interface:
interface Animal {
ping(): void;
}
class Dog implements Animal {
ping(): void {
console.log("旺!");
}
}
// 报错:
// 类“Cat”错误实现接口“Animal”:
// 类型 "Cat" 中缺少属性 "ping",但类型 "Animal" 中需要该属性.
class Cat implements Animal {
pong(): void {
console.log("喵!");
}
}
类也可以实现多个接口,例如 class C implements A, B
注意:implements
The clause just checks whether the type of the class conforms to a specific interface,它根本不会改变类的类型或其方法,如:A class implementing an interface with an optional property does not create that property:
extends子句
类可以从基类中扩展出来(称为派生类),A derived class has all the properties and methods of its base class,也可以定义额外的成员:
class Animal {
move() {
console.log("move");
}
}
class Dog extends Animal {
woof() {
console.log("woof");
}
}
const d = new Dog();
// 基类的类方法
d.move();
// Derived class's own class methods
d.woof();
注意:
在【TypeScript】深入学习TypeScript对象类型扩展类型In the section we said that the interface can be usedextends
Extend from multiple types:extends User, Age
而类使用extends
Only one class can be extended:
重写方法
Derived classes can override fields or properties of the base class,并且可以使用super.
语法来访问基类方法:
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
// 在Derived中重写greet方法
greet(name?: string) {
if (name === undefined) {
// 调用基类的greet方法
super.greet();
} else {
console.log(`Hello, ${
name.toUpperCase()}`);
}
}
}
const d = new Derived();
d.greet(); // "Hello, world!"
d.greet("reader"); // "Hello, READER"
It is legal to refer to a derived class instance through a base class reference,and is very common:
// 通过基类引用来引用派生类实例
// bThe type refers to the base classBase,But it can be citedBase的派生类实例
const b: Base = new Derived();
// 没问题
b.greet();
TypeScript
It is mandatory that a derived class is always one of its base classes子类型,If the agreement is violated, an error will be reported:
上面报错是因为“(name: string) => void
”不是类型“() => void
”的子类型,while previously used“(name?: string) => void
”才是“() => void
”子类型
Does anyone here feel like I'm saying the opposite,会感觉() => void
是(name?: string) => void
subtype of ,So let me test my claim:
type A = () => void;
type B = (name?: string) => void;
type C = B extends A ? number : string;
const num: C = 1;
这里可以看到num
是number
类型,则type C=number
,则B extends A
成立,所以A
是B
的基类,B
是从A
扩展来的,则称B
是A
的子类型,This confirms the above conclusion
其实这里子类型的"子"Not that it's part of who it is,Rather, it is who it inherits from
For example the type aboveA
和B
,If only in terms of scope,B
肯定是包含A
的,但就因为B
是在A
expanded on the basis of ,是继承的A
,所以无论B
范围比A
大多少,它仍然是A
的子类型
It's as if we humans had children,regardless of the child's ability,Eyes bigger than the parents,He is still a child of his parents
初始化顺序
类初始化的顺序是:
- 基类的字段被初始化
- 基类构造函数运行
- 派生类的字段被初始化
- 派生类构造函数运行
class Base {
name = "base";
constructor() {
console.log(this.name);
}
}
class Derived extends Base {
name = "derived";
}
// 打印 "base", 而不是 "derived"
const d = new Derived();
继承内置类型
注意:如果你不打算继承
Array
、Error
、Map
等内置类型,或者你的编译目标明确设置为ES6/ES2015
或以上,你可以跳过这一部分.
在ES6/ES2015
中,The constructor that returns the object implicitly overrides any callssuper(...)
的this
的值.生成的构造函数代码有必要捕获super(...)
的任何潜在返回值并将其替换为this
因此,子类化Error
、Array
等可能不再像预期那样工作.这是由于Error
、Array
等的构造函数使用ES6
的new.target
来调整原型链;然而,在ES5
中调用构造函数时,没有办法确保new.target
的值.默认情况下,Other low-level compilers(ES5
以下)Usually have the same restrictions.
See a subclass below:
class MsgError extends Error {
constructor(m: string) {
super(m);
}
sayHello() {
// this.message为基类Error上的属性
return "hello " + this.message;
}
}
const msgError = new MsgError("hello");
console.log(msgError.sayHello());
上述代码,in programmingES6
及以上版本的JS
后,能够正常运行,但当我们修改tsconfig.json
的target
为ES5
时,使其编译成ES5
版本的,你可能会发现:
方法在构造这些子类所返回的对象上可能是未定义的,所以调用
sayHello
会导致错误.instanceof
将在子类的实例和它们的实例之间被打破,所以new MsgError("hello") instanceof MsgError)
将返回false
:console.log(new MsgError("hello") instanceof MsgError); // false
官方建议,可以在任何super(...)
调用后立即手动调整原型:
class MsgError extends Error {
constructor(m: string) {
super(m);
// 明确地设置原型.
// 将thisThe prototype on is set to MsgError的原型
Object.setPrototypeOf(this, MsgError.prototype);
}
sayHello() {
// this.message为基类Error上的属性
return "hello " + this.message;
}
}
const msgError = new MsgError("hello");
console.log(msgError.sayHello()); // hello hello
console.log(new MsgError("hello") instanceof MsgError); // true
MsgError
的任何子类也必须手动设置原型.对于不支持Object.setPrototypeOf
的运行时,可以使用__proto__
来代替:
class MsgError extends Error {
// 先声明一下__proto__,Its type is the current class
// 不然调用this.__proto__时会报:类型“MsgError”上不存在属性“__proto__”
__proto__: MsgError;
constructor(m: string) {
super(m);
// 明确地设置原型.
this.__proto__ = MsgError.prototype;
}
sayHello() {
// this.message为基类Error上的属性
return "hello " + this.message;
}
}
const msgError = new MsgError("hello");
console.log(msgError.sayHello()); // hello hello
console.log(new MsgError("hello") instanceof MsgError); // true
不幸的是,这些变通方法在Internet Explorer 10
和更早的版本上不起作用.我们可以手动将原型中的方法复制到实例本身(例如MsgError.prototype
到this
),但是原型链本身不能被修复.
3、成员的可见性
可以使用TypeScript
来控制某些方法或属性对类外的代码是否可见
public
public
定义公共属性,is the default visibility of class members,可以在任何地方被访问:
class Greeter {
public greet() {
console.log("hi!");
}
}
const g = new Greeter();
g.greet();
因为public
已经是默认的可见性修饰符,So generally you don't need to write it on class members,但为了风格/可读性的原因,可能会选择这样做
protected
protected
定义受保护成员,Visible only to the class in which they are declared and its subclasses:
class Greeter {
protected name = "Ailjx";
greet() {
console.log(this.name);
}
}
class Child extends Greeter {
childGreet() {
console.log(this.name);
}
}
const g = new Greeter();
const c = new Child();
g.greet(); // Ailjx
c.childGreet(); // Ailjx
// 报错:属性“name”受保护,只能在类“Greeter”及其子类中访问.
console.log(g.name); // 无权访问
Expose protected members
派生类需要遵循它们的基类契约,但可以选择公开具有更多能力的基类的子类型,这包括将受保护的成员变成公开:class Base { protected m = 10; } class Derived extends Base { // A maintained property of the base classmhas been changed to public // 没有修饰符,So it defaults to publicpublic m = 15; } const d = new Derived(); console.log(d.m); // OK
private
private
定义私有属性,比protected
还要严格,It only allows access within the current class
class Base {
private name = "Ailjx";
greet() {
// 只能在当前类访问
console.log(this.name);
}
}
class Child extends Base {
childGreet() {
// 不能在子类中访问
// 报错:属性“name”为私有属性,只能在类“Base”中访问
console.log(this.name);
}
}
const b = new Base();
// 不能在类外访问
// 报错:属性“name”为私有属性,只能在类“Base”中访问.
console.log(b.name); // 无权访问
private
Allows access using bracket notation when type checking:
console.log(b["name"]); // "Ailjx"
因为私有private
成员对派生类是不可见的,So derived classes cannot be used like protected
to increase its visibility
Access across instances
TypeScript
Different instances of the same class can access each other's private properties:class A { private x = 0; constructor(x: number) { this.x = x; } public sameAs(other: A) { // 可以访问 return other.x === this.x; } } const a1 = new A(1); const a2 = new A(10); const is = a1.sameAs(a2); console.log(is); // false
参数属性
TypeScript
提供了特殊的语法,可以将构造函数参数becomes a class property with the same name and value,这些被称为参数属性,通过在构造函数参数前加上可见性修饰符 public
、private
、protected
或readonly
中的一个来创建,由此产生的字段会得到这些修饰符:
class A {
// cOptional property that is private
constructor(public a: number, protected b: number, private c?: number) {
}
}
const a = new A(1, 2, 3);
console.log(a.a); // 1
注意事项
像TypeScript
类型系统的其他方面一样, private
和protected
只在Type checking is enforced,这意味着在JavaScript
的运行时结构,如in
或简单的属性查询,仍然可以访问一个私有或保护的成员:
class MySafe {
private secretKey = 12345;
}
const s = new MySafe();
// 报错:属性“secretKey”为私有属性,只能在类“MySafe”中访问.
console.log(s.secretKey);
上方TS
The code throws an error though,But when we run its compiledJS
When the file is displayed, it will be found that it is normally printed out12345
这意味着 private
和protected
It only serves as an error message,Doesn't really limit the compiled onesJS
文件,i.e. these fields are软sexually private,不能严格执行私有特性
与TypeScript
的 private
不同,JavaScript
的private
字段(#
)在编译后仍然是private
的,并且不提供前面提到的像括号符号访问那样的转义窗口,使其成为硬private
:
class Dog {
#barkAmount = 0;
constructor() {
console.log(this.#barkAmount); // 0
}
}
const dog = new Dog();
// TS报错:类型“Dog”上不存在属性“barkAmount”,编译后的JS运行时打印undefined
console.log(dog.barkAmount);
// TS报错:属性 "#barkAmount" 在类 "Dog" 外部不可访问,because it has a dedicated identifier.
// 编译后的JS也直接报错
console.log(dog.#barkAmount);
The above code is compiled toES2021
或更低版本时,TypeScript
将使用WeakMaps来代替 #
:
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Dog_barkAmount;
class Dog {
constructor() {
_Dog_barkAmount.set(this, 0);
console.log(__classPrivateFieldGet(this, _Dog_barkAmount, "f")); // 0
}
}
_Dog_barkAmount = new WeakMap();
const dog = new Dog();
// TS报错:类型“Dog”上不存在属性“barkAmount”,编译后的JS运行时打印undefined
console.log(dog.barkAmount);
// TS报错:属性 "#barkAmount" 在类 "Dog" 外部不可访问,because it has a dedicated identifier.
// 编译后的JS也直接报错
console.log(dog.);
如果你需要保护你的类中的值免受恶意行为的影响,You should use provide硬Mechanisms for runtime privacy,如闭包、
WeakMaps
或私有字段.请注意,这些在运行时增加的隐私检查可能会影响性能.
4、静态成员
类可以有静态成员,这些成员并不与类的特定实例相关联,它们可以通过类的构造函数对象本身来访问:
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
// 等同于console.log(this.x);
}
}
// Static members are not requirednew
console.log(MyClass.x); // 0
MyClass.printX(); // 0
静态成员也可以使用相同的public
、protected
和private
可见性修饰符:
class MyClass {
private static x = 0;
static printX() {
// ok
console.log(MyClass.x);
// 等同于console.log(this.x);
}
}
// Static members are not requirednew
// TS报错:属性“x”为私有属性,只能在类“MyClass”中访问
console.log(MyClass.x);
// ok
MyClass.printX(); // 0
静态成员也会被继承:
class Base {
static BaseName = "Ailjx";
}
class Derived extends Base {
// 基类的静态成员BaseName被继承了
static myName = this.BaseName;
}
console.log(Derived.myName, Derived.BaseName); // Ailjx Ailjx
特殊静态名称
一般来说,从函数原型覆盖属性是不安全的/不可能的,因为类本身就是可以用new
调用的函数,所以某些静态名称不能使用,像name
、length
和call
这样的函数属性,定义为静态成员是无效的:
没有静态类
TypeScript
(和JavaScript
)没有像C#
和Java
那样有一个叫做静态类的结构,这些结构体的存在,只是因为这些语言强制所有的数据和函数都在一个类里面
因为这个限制在TypeScript
中不存在,所以不需要它们,一个只有一个实例的类,在JavaScript
/TypeScript
中通常只是表示为一个普通的对象
例如,我们不需要TypeScript
中的 "静态类 "语法,因为一个普通的对象(甚至是顶级函数)也可以完成这个工作:
// 不需要 "static" class
class MyStaticClass {
static doSomething() {
}
}
// 首选 (备选 1)
function doSomething() {
}
// 首选 (备选 2)
const MyHelperObject = {
dosomething() {
},
};
5、静态块
static
静态块允许你写一串有自己作用域的语句,可以访问包含类中的私有字段,这意味着我们可以用写语句的所有能力来写初始化代码,不泄露变量,并能完全访问我们类的内部结构:
class Foo {
static #count = 0;
get count() {
return Foo.#count;
}
static {
try {
Foo.#count += 100;
console.log("初始化成功!");
} catch {
console.log("初始化错误!");
}
}
}
const a = new Foo(); // 初始化成功
console.log(a.count); // 100
结语
至此,TypeScript
The first part of the class is over,关注博主下篇更精彩!
博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持.
参考资料:TypeScript官网
如果本篇文章对你有所帮助,还请客官一件四连!️
边栏推荐
猜你喜欢
随机推荐
源码构建LAMP环境-2
你我都会遇到的需求:如何导出MySQL中的数据~ 简单!实用!
Redis是如何轻松实现系统秒杀的?
回文自动机+CodeTON Round 2 C,D
圆锥折射作为偏振计量工具的模拟
树形结构构造示例代码
hi!Don't look at how to SAO gas dye-in-the-wood in MySQL?
golang刷leetcode:最大波动的子字符串
面试官:可以谈谈乐观锁和悲观锁吗
面试官居然问我:删库后,除了跑路还能干什么?
【3D视觉】realsense D435三维重建
apache calcite中关于model文件配置
golang刷leetcode:使数组按非递减顺序排列
UDP (User Datagram Protocol)
Vscode快速入门、 插件安装、插件位置、修改vscode默认引用插件的路径、在命令行总配置code、快捷键
Command line startup FAQs and solutions
SRv6网络演进面临的挑战
Summary of @Transactional transaction invocation and effective scenarios
[c] Detailed explanation of operators (1)
面试了个985毕业的,回答“性能调优”题时表情令我毕生难忘