Skip to content

Translate Handbookv2/Classes in Chinese #177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

YexuanXiao
Copy link

No description provided.

@microsoft-github-policy-service

@YexuanXiao please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

@github-actions
Copy link
Contributor

Thanks for the PR!

This section of the codebase is owned by @Kingwl - if they write a comment saying "LGTM" then it will be merged.

@github-actions
Copy link
Contributor

Translation of Classes.md

title: class
layout: docs
permalink: /zh/docs/handbook/2/classes.html

oneline: "How TypeScript's classes work"

背景阅读:
Classes (MDN)

TypeScript provides support for what was introduced in ES2015 class Full support for keywords.

Like other JavaScript features, TypeScript adds type annotations and other syntax to allow you to express relationships between classes and other types.

Class members

Here is a basic class - an empty class:

class Point {}

This class isn't very useful yet, so let's start adding some members.

field

The field declaration creates an exposed writable property in the class:

// @strictPropertyInitialization: false
class Point {
  x: number;
  y: number;
}

const pt = new Point();
pt.x = 0;
pt.y = 0;

As with the other sections, type annotations are optional, but if not specified, they are implicitly declared as any

Fields are also available Initializer; The initializer will execute automatically when the class is instantiated.

class Point {
  x = 0;
  y = 0;
}

const pt = new Point();
// 打印 0, 0
console.log(`${pt.x}, ${pt.y}`);

resemble constlet and var As such, the initializer of a class's properties can be used to derive its type.

// @errors: 2322
class Point {
  x = 0;
  y = 0;
}
// ---cut---
const pt = new Point();
pt.x = "0";

--strictPropertyInitialization

strictPropertyInitialization(Strict property initialization)The setting controls whether class fields need to be initialized in the constructor.

// @errors: 2564
class BadGreeter {
  name: string;
}
class GoodGreeter {
  name: string;

  constructor() {
    this.name = "hello";
  }
}

Note that the field is required in the Initialization in the constructor
TypeScript does not analyze initialization of calling other methods from the constructor because derived classes may override those methods and fail to initialize those members.

You can use if you explicitly intend to initialize fields by means other than constructors (for example perhaps an external library populates your class). Determine the assignment assertion operator !

class OKGreeter {
  // 未初始化,但不是错误
  name!: string;
}

readonly

Fields can have readonly modifier as a prefix.
This protects assignments to the field outside of the constructor.

// @errors: 2540 2540
class Greeter {
  readonly name: string = "world";

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

  err() {
    this.name = "不行";
  }
}
const g = new Greeter();
g.name = "也不行";

Constructor

背景阅读:
Constructor (MDN)

The constructor of a class is very similar to a normal function.
You can add type annotations, default values, and overloads to parameters:

class Point {
  x: number;
  y: number;

  // 具有默认值的普通签名
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}
class Point {
  // 重载
  constructor(x: number, y: string);
  constructor(s: string);
  constructor(xs: any, y?: any) {
    // 待定
  }
}

There are only a few differences between a class's constructor signature and a function signature:

  • The constructor cannot have type parameters - these parameters belong to the external class declaration, which we will learn about later
  • The constructor cannot have a return type annotation - the returned content is always a class instance type

Super called

Just like in JavaScript, if you have a base class, you need to use any this. The member is previously called in the constructor body super();

// @errors: 17009
class Base {
  k = 4;
}

class Derived extends Base {
  constructor() {
    // 在 ES5 中打印错误的值;在 ES6 中抛出异常
    console.log(this.k);
    super();
  }
}

Forgot to call super It's an easy mistake to make in JavaScript, but TypeScript will tell you when necessary.

method

背景阅读:
Method definitions

The properties of the function type in a class are called method
Methods can use the same type annotations as functions and constructors:

class Point {
  x = 10;
  y = 10;

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

TypeScript does not add anything new to methods other than standard type annotations.

Note that inside the method, it still needs to be passed this Access fields and other methods.
Unqualified names in the method body will always refer to content in enclosing scope:

// @errors: 2322
let x: number = 0;

class C {
  x: string = "hello";

  m() {
    // 这将试图修改第 1 行中的 `x`,而不是类的属性
    x = "world";
  }
}

Getters / Setters

Classes can also have Accessor

class C {
  _length = 0;
  get length() {
    return this._length;
  }
  set length(value) {
    this._length = value;
  }
}

Note that get/set fields without extra logic are of little use in JavaScript.
If you don't need to add additional logic in your get/set operation, you can expose it as a public field.

TypeScript has some special derivation rules for accessors:

  • If get Exists, but set If it does not exist, the property is automatically readonly
  • If the type of the setter parameter is not specified, it can be inferred from the return type of the getter
  • Getters and Setters must be the same Member visibility

from TypeScript 4.3 Initially, the type parameter of the setter allows the use of union types.

class Thing {
  _size = 0;

  get size(): number {
    return this._size;
  }

  set size(value: string | number | boolean) {
    let num = Number(value);

    // 不允许 NaN, Infinity, 等等
    if (!Number.isFinite(num)) {
      this._size = 0;
      return;
    }

    this._size = num;
  }
}

Index signature

Classes can declare index signatures; andIndex signatures for other object typesDo the same job:

class MyClass {
  [s: string]: boolean | ((s: string) => boolean);

  check(s: string) {
    return this[s] as boolean;
  }
}

Because indexed signature types also require capturing the types of methods, it is not easy to use them effectively.
In general, it's better to store index data in another location than on the class instance itself.

Class hierarchy

Like other languages with object-oriented functionality, classes in JavaScript can inherit from base classes.

implements clause

You can use implements clause to check whether the class satisfies a specific interface
If the class does not implement it correctly, it produces an error:

// @errors: 2420
interface Pingable {
  ping(): void;
}

class Sonar implements Pingable {
  ping() {
    console.log("ping!");
  }
}

class Ball implements Pingable {
  pong() {
    console.log("pong!");
  }
}

Classes can also implement multiple interfaces, for example class C implements A, B {

warn

One important point to understand:implements The clause only checks whether the class can be treated as an interface type.
it completely The type of the class or its methods are not changed.
A common source of error is assumptions implements The clause will change the class type - it won't!

// @errors: 7006
interface Checkable {
  check(name: string): boolean;
}

class NameChecker implements Checkable {
  check(s) {
    // 注意,此处没有错误
    return s.toLowercse() === "ok";
    //         ^?
  }
}

In this example, we might expect s The type will be affected by check target name: string The influence of parameters.
It's not - implements The clause does not change how the class body or its type inference is checked.

Similarly, implementing an interface with an optional property does not create that property:

// @errors: 2339
interface A {
  x: number;
  y?: number;
}
class C implements A {
  x = 0;
}
const c = new C();
c.y = 10;

extends clause

背景阅读:
extends keyword (MDN)

The class can be derived from the base class extends
A derived class has all the properties and methods of its base class and can define additional members.

class Animal {
  move() {
    console.log("继续前进!");
  }
}

class Dog extends Animal {
  woof(times: number) {
    for (let i = 0; i < times; i++) {
      console.log("汪!");
    }
  }
}

const d = new Dog();
// 基类方法
d.move();
// 派生类方法
d.woof(3);

Override the method

背景阅读:
super keyword (MDN)

Derived classes can also override base class fields or properties.
Can be used super. syntax to access base class methods.
Note that since JavaScript's class is a simple lookup object, there is no concept of a "super field".

TypeScript forces a derived class to always be a subtype of its base class.

For example, here are the legitimate ways to override a method:

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

The important point is that derived classes abide by their base class conventions.
Remember, referencing derived class instances through base class references is very common (and always legal!). ):

class Base {
  greet() {
    console.log("Hello, world!");
  }
}
declare const d: Base;
// ---cut---
// 通过基类的引用引用派生类的实例
const b: Base = d;
// 没问题
b.greet();

If Derived Failure to comply Base What about the agreement?

// @errors: 2416
class Base {
  greet() {
    console.log("Hello, world!");
  }
}

class Derived extends Base {
  // 使这个参数为必要
  greet(name: string) {
    console.log(`Hello, ${name.toUpperCase()}`);
  }
}

If we compile this code incorrectly, this example will trigger a crash:

declare class Base {
  greet(): void;
}
declare class Derived extends Base {}
// ---cut---
const b: Base = new Derived();
// 崩溃,因为 `name` 未定义
b.greet();

Pure type field declaration

while target >= ES2022 or useDefineForClassFields for true , the class field is initialized after the parent class constructor completes, overriding any values set by the parent class. This can be a problem when you just want to redeclare an inherited field to a more accurate type. To handle this situation, you can use declare to indicate to TypeScript that this field declaration should not have run-time effect.

interface Animal {
  dateOfBirth: any;
}

interface Dog extends Animal {
  breed: any;
}

class AnimalHouse {
  resident: Animal;
  constructor(animal: Animal) {
    this.resident = animal;
  }
}

class DogHouse extends AnimalHouse {
  // 不会生成 JavaScript 代码,
  // 只确保类型正确
  declare resident: Dog;
  constructor(dog: Dog) {
    super(dog);
  }
}

Initialization order

In some cases, the initialization order of JavaScript classes can be surprising.
Let's consider the following code:

class Base {
  name = "基";
  constructor() {
    console.log("我的名字是 " + this.name);
  }
}

class Derived extends Base {
  name = "派生";
}

// 打印 "基" 而不是 "派生"
const d = new Derived();

What happened?

The sequence of class initialization according to JavaScript is as follows:

  • Initialize the base class field
  • The base class constructor runs
  • Initialize the derived class field
  • The derived class constructor runs

This means that the base class constructor sees its own in its own constructor name value because initialization of the derived class field has not yet run.

Inherit built-in types

Note: If you do not plan to inherit Array, Error, Map and other built-in types, or your compilation target is explicitly set to ES6/ES2015 or later, you can skip this section

In ES2015, the constructor that returns the object is implicitly converted this to be replaced by super(...) Any caller.
The generated constructor code must be captured super(...) any potential return value and replace it this

Therefore, inheritance ErrorArray and other types may not work as expected.
This is because ErrorArray etc. The constructor uses ECMAScript 6 new.target to adjust the prototype chain;
However, when the constructor is called in ECMAScript 5, it cannot be ensured new.target value.
By default, other downward compilers usually have the same limitations.

For the subclass as follows:

class MsgError extends Error {
  constructor(m: string) {
    super(m);
  }
  sayHello() {
    return "hello " + this.message;
  }
}

You may find:

  • The method may be a subtype returned to one undefined object, therefore sayHello will result in an error.
  • instanceof May fail between subclasses and their instances, so (new MsgError()) instanceof MsgError will return false

As a suggestion, you can do it manually at all super(...) Adjust the prototype immediately before invoking.

class MsgError extends Error {
  constructor(m: string) {
    super(m);

    // 显式设置原型。
    Object.setPrototypeOf(this, MsgError.prototype);
  }

  sayHello() {
    return "hello " + this.message;
  }
}

ButMsgError Any subclass must also be prototyped manually.
For not supported Object.setPrototypeOf You can use the runtime __proto__ Substitute.

Unfortunately,These workarounds do not apply to IE 10 and earlier.
You can manually copy methods from the prototype to the instance itself (eg MsgError.prototype arrive this), but the prototype chain itself cannot be fixed.

Member visibility

TypeScript can control whether certain methods or properties are visible to code outside the class.

public

The default visibility of class members is public
public Members can be accessed from anywhere:

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

because public is the default visibility modifier, so never need Write it on a class member, but may choose to do so for stylistic/readability reasons.

protected

protected Members are visible only to subclasses of that class.

// @errors: 2445
class Greeter {
  public greet() {
    console.log("Hello, " + this.getName());
  }
  protected getName() {
    return "嗨!";
  }
}

class SpecialGreeter extends Greeter {
  public howdy() {
    // 可以在此处访问受保护的成员
    console.log("Howdy, " + this.getName());
    //                          ^^^^^^^^^^^^^^
  }
}
const g = new SpecialGreeter();
g.greet(); // 可行
g.getName();

Exposure of protected members

Derived classes need to adhere to the conventions of their base classes, but may choose to expose subtypes of base classes with more functionality.
This includes making protected member public

class Base {
  protected m = 10;
}
class Derived extends Base {
  // 无修饰符,因此默认值为 `public`
  m = 15;
}
const d = new Derived();
console.log(d.m); // 可行

note Derived Already able to read and write freely m, so this does not meaningfully change the "security" of this situation.
The main point to note here is that in derived classes, we need to be careful to repeat if in order not to expose them intentionally protected modifiers.

Across levels protected visit

Different OOP language pairs are accessed through base class references protected There is disagreement about whether members are legitimate:

// @errors: 2446
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: Base) {
    other.x = 10;
  }
}

For example, Java considers this to be legal.
C# and C++, on the other hand, decide that this code is illegal.

TypeScript stands with C# and C++ because only passes Derived2 and its subtype access x is legal,Derived1 Not included.
Also, if by one Derived1 visit x Legally (of course!) ), and then accessing it through a reference to the base class will never improve the situation.

See Why can't I access protected members from derived classes? for more C# explanation of this.

private

private similar protected, but disables subclass access to members:

// @errors: 2341
class Base {
  private x = 0;
}
const b = new Base();
// 无法从类外部访问
console.log(b.x);
// @errors: 2341
class Base {
  private x = 0;
}
// ---cut---
class Derived extends Base {
  showX() {
    // 无法从子类访问
    console.log(this.x);
  }
}

because private Members are not visible to derived classes, so derived classes cannot increase their visibility:

// @errors: 2415
class Base {
  private x = 0;
}
class Derived extends Base {
  x = 1;
}

Across levels private visit

Different OOP languages can access each other for different instances of the same class private Members are divided.
Languages like C++, Java, C#, Swift, and PHP allow this, but Ruby doesn't.

TypeScript allows cross-hierarchies private Visit:

class A {
  private x = 10;

  public sameAs(other: A) {
    // 没有错误
    return other.x === this.x;
  }
}

warn

As with other aspects of TypeScript's type system,private and protected Enforcement only during type checking

This means that JavaScript runtime constructs such as in or simple property lookup) is still accessible private or protected Member:

class MySafe {
  private secretKey = 12345;
}
// 在 JavaScript 文件中...
const s = new MySafe();
// 将打印 12345
console.log(s.secretKey);

private Access using parenthetical notation is also allowed during type checking. This allows: private Declared fields may be easier to access by things like unit tests, the downside is that these fields are Soft private And private policies are not strictly enforced.

// @errors: 2341
class MySafe {
  private secretKey = 12345;
}

const s = new MySafe();

// 类型检查期间不允许
console.log(s.secretKey);

// 可行
console.log(s["secretKey"]);

Not like TypeScripts private, JavaScriptPrivate fields#Keep them private after compilation and do not provide the previously mentioned escape exits, such as bracket notation access, to make them Hard private

class Dog {
  #barkAmount = 0;
  personality = "高兴";

  constructor() {}
}
// @target: esnext
// @showEmit
class Dog {
  #barkAmount = 0;
  personality = "高兴";

  constructor() {}
}

When compiling to ES2021 or earlier, TypeScript will use WeakMaps instead #

// @target: es2015
// @showEmit
class Dog {
  #barkAmount = 0;
  personality = "happy";

  constructor() {}
}

If you need to protect values in a class from malicious actors, you should use mechanisms that provide hard runtime privacy, such as closures, WeakMaps, or private fields. Note that these private checks added at runtime can affect performance.

Static members

背景阅读:
Static Members (MDN)

Classes can have 静态 Member.
These members are not associated with a specific instance of the class.
They can be accessed through the class constructor object itself:

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

Static members can also have publicprotected and private Visibility modifiers:

// @errors: 2341
class MyClass {
  private static x = 0;
}
console.log(MyClass.x);

Static members are also inherited:

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

Special static member name

Usually, override Function The properties of the prototype are not safe/impossible.

Since the class itself is usable new The function is called, so some names cannot be as static Use.
Function properties for example name, length and call Cannot be legally defined static Member:

// @errors: 2699
class S {
  static name = "S!";
}

Why are there no static classes?

TypeScript (and JavaScript) don't have the same name as C# static class of construction.

These constructs only Exists with a language that forces all data and functions to be in one class. Because the restrictions do not exist in TypeScript, they are not needed.
A class with only one instance in JavaScript/TypeScript is typically just a plain one object

For example, we don't need the "static class" syntax in TypeScript, because ordinary objects (even top-level functions) can do the job:

// 不必要的“静态”类
class MyStaticClass {
  static doSomething() {}
}

// 首选(备选 1)
function doSomething() {}

// 次选 (备选 2)
const MyHelperObject = {
  dosomething() {},
};

in the class static block

Static blocks allow you to write a series of statements with their own scope that can access private fields in this class. This means that we can write initialization code with full functionality, no leaking variables, and full access to the internal structure of our class.

declare function loadLastInstances(): any[]
// ---cut---
class Foo {
    static #count = 0;

    get count() {
        return Foo.#count;
    }

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

Generic classes

Like interfaces, classes can be generic.
When used by generic classes new When instantiated, its type parameters are inferred in the same way as in a function call:

class Box<Type> {
  contents: Type;
  constructor(value: Type) {
    this.contents = value;
  }
}

const b = new Box("hello!");
//    ^?

Classes can use generic constraints and default values just like interfaces.

The type parameter of the static member

This code is not legal for reasons that may not be obvious:

// @errors: 2302
class Box<Type> {
  static defaultValue: Type;
}

Remember that types are always completely erased!
At runtime, only One Box.defaultValue Attribute.
This means setting Box<string>.defaultValue(if possible)also change Box<number>.defaultValue -Not good.
of generic classes static Members can never reference a type parameter of a class.

class this Performance at runtime

背景阅读:
this keyword (MDN)

It's important to remember that TypeScript doesn't change JavaScript's runtime behavior, and JavaScript is known for its special runtime behavior.

JavaScript pairs this The treatment is indeed unusual:

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

// 打印“对象”,而不是“我的类”
console.log(obj.getName());

Long story short, by default in the function this The value depends How the function is called.
In this example, because the function is through obj The reference is called, therefore this be obj instead of a class instance.

This is not what you want!
TypeScript provides methods to mitigate or prevent such errors.

Arrow functions

背景阅读:
Arrow functions (MDN)

If you have one usually to lose it this The context of the function that is called can be intentionally used using arrow function properties instead of method definitions:

class MyClass {
  name = "我的类";
  getName = () => {
    return this.name;
  };
}
const c = new MyClass();
const g = c.getName;
// 打印“我的类”,而不是崩溃
console.log(g());

This requires some trade-offs:

  • this The value is guaranteed to be correct at run time, even for code that is not checked with TypeScript
  • This will use more memory because each class instance defines a copy of each of its own functions in this way
  • Cannot be used in derived classes super.getName, because there is no entry in the prototype chain to get the base class method from

this parameter

In the method or function definition, named this The starting parameter has a special meaning in TypeScript.
These parameters are removed during compilation:

type SomeType = any;
// ---cut---
// TypeScript 使用 'this'
function fn(this: SomeType, x: number) {
  /* ... */
}
// 输出的 JavaScript
function fn(x) {
  /* ... */
}

TypeScript check used this Whether the function called by the parameter was done in the correct context.
We can add one to the method definition this Arguments, instead of using arrow functions, statically force to ensure that the method is called correctly:

// @errors: 2684
class MyClass {
  name = "MyClass";
  getName(this: MyClass) {
    return this.name;
  }
}
const c = new MyClass();
// 可行
c.getName();

// 错误,导致崩溃
const g = c.getName;
console.log(g());

This method and the arrow function method require opposite trade-offs:

  • JavaScript callers may still use class methods incorrectly without realizing it.
  • Each class definition allocates only one function, rather than one function per class instance.
  • Base class method definitions are still passable super Invoke.

this type

In the class, named this The special type will dynamic The type that refers to the current class.
Let's see what it's for:

class Box {
  contents: string = "";
  set(value: string) {
//  ^?
    this.contents = value;
    return this;
  }
}

Here TypeScript deduces set The return value is this Instead of Box
Let's make one Box Subtype:

class Box {
  contents: string = "";
  set(value: string) {
    this.contents = value;
    return this;
  }
}
// ---cut---
class ClearableBox extends Box {
  clear() {
    this.contents = "";
  }
}

const a = new ClearableBox();
const b = a.set("hello");
//    ^?

You can also use it in parameter type annotations this

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

This is the same as writing other: Box Different - if you have a derived class, it's sameAs The method will now only accept other instances of the same derived class:

// @errors: 2345
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);

Based on this of protection

You can use it in the return position of methods in classes and interfaces this is Type
When mixed with type narrowing (eg if statement), the type of the target object is narrowed to the specified type Type

// @strictPropertyInitialization: false
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;
// ^?
} else if (fso.isDirectory()) {
  fso.children;
// ^?
} else if (fso.isNetworked()) {
  fso.host;
// ^?
}

A common use of this type of protection is to allow deferred validation of specific fields. For example, when hasValue When verified to be true, this condition is removed from the value in the box undefined

class Box<T> {
  value?: T;

  hasValue(): this is { value: T } {
    return this.value !== undefined;
  }
}

const box = new Box();
box.value = "Gameboy";

box.value;
//  ^?

if (box.hasValue()) {
  box.value;
  //  ^?
}

Parameter properties

TypeScript provides special syntax for converting constructor parameters to class properties with the same name and value.
This syntax is called _Parameter properties_by adding one on the constructor's parameters publicprivateprotected or readonly Prefix complete.
The resulting field gets the following modifier:

// @errors: 2341
class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // 不再需要 body
  }
}
const a = new Params(1, 2, 3);
console.log(a.x);
//            ^?
console.log(a.z);

Class expressions

背景阅读:
Class expressions (MDN)

Class expressions are very similar to class declarations.
The only real difference is that class expressions don't need names, although we can refer to them by any identifier they are bound to:

const someClass = class<Type> {
  content: Type;
  constructor(value: Type) {
    this.content = value;
  }
};

const m = new someClass("Hello, world");
//    ^?

abstract Classes and members

Classes, methods, and fields in TypeScript can be Abstract.

One Abstract methods or Abstract fields is a declaration that no implementation is provided.
These members must be present Abstract classes, which cannot be instantiated directly.

The role of an abstract class is to act as a base class for subclasses that implement all abstract members.
When a class does not have any abstract members, it is called Specific classes

Let's look at an example:

// @errors: 2511
abstract class Base {
  abstract getName(): string;

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

const b = new Base();

We can't use it new to instantiate Base, because it is abstract.
Instead, we need to create a derived class and implement the abstract members:

abstract class Base {
  abstract getName(): string;
  printName() {}
}
// ---cut---
class Derived extends Base {
  getName() {
    return "world";
  }
}

const d = new Derived();
d.printName();

Note that if we forget to implement an abstract member of the base class, we will get an error:

// @errors: 2515
abstract class Base {
  abstract getName(): string;
  printName() {}
}
// ---cut---
class Derived extends Base {
  // 忘记做任何事情
}

Abstract construct signatures

Sometimes you want to accept a class constructor that generates an instance of a class derived from an abstract class.

For example, you might want to write the following code:

// @errors: 2511
abstract class Base {
  abstract getName(): string;
  printName() {}
}
class Derived extends Base {
  getName() {
    return "";
  }
}
// ---cut---
function greet(ctor: typeof Base) {
  const instance = new ctor();
  instance.printName();
}

TypeScript correctly tells you that you are trying to instantiate an abstract class.
After all, take into account greet , it's perfectly legal to write this code, and you end up trying to construct an abstract class:

declare const greet: any, Base: any;
// ---cut---
// 坏!
greet(Base);

Instead, you want to write a function that accepts content with a construct signature:

// @errors: 2345
abstract class Base {
  abstract getName(): string;
  printName() {}
}
class Derived extends Base {
  getName() {
    return "";
  }
}
// ---cut---
function greet(ctor: new () => Base) {
  const instance = new ctor();
  instance.printName();
}
greet(Derived);
greet(Base);

Now, TypeScript correctly tells you which class constructors you can call - Derived Yes, as it is specific, but Base No.

Relationships between classes

In most cases, TypeScript compares classes structurally to other types.

For example, these two classes can replace each other because they are the same:

class Point1 {
  x = 0;
  y = 0;
}

class Point2 {
  x = 0;
  y = 0;
}

// 可行
const p: Point1 = new Point2();

Similarly, subtype relationships exist between classes even without explicit inheritance:

// @strict: false
class Person {
  name: string;
  age: number;
}

class Employee {
  name: string;
  age: number;
  salary: number;
}

// 可行
const p: Person = new Employee();

It sounds simple, but there are some situations that seem stranger than others.

An empty class has no members.
In a structured type system, a type without members is usually a superclass for anything else.
So if you write an empty class (don't!) ), anything can be used in place of it:

class Empty {}

function fn(x: Empty) {
  // 不能用 'x’ 做任何事情,所以我不会
}

// 一切都如所愿!
fn(window);
fn({});
fn(fn);

Generated by 🚫 dangerJS against 5417e2f

@YexuanXiao
Copy link
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant