Typescript learning document – Advanced

Time:2022-4-15

catalogue

Typescript learning advanced Chapter 1: variable declaration

letandconstAre two relatively new concepts of variable declaration in JavaScript. As we mentioned earlier,letIn some ways withvarSimilar, but allows users to avoid some common “troubles” in JavaScript.

constyesletAn extension to prevent reassignment to a variable.

Since typescript is an extension of JavaScript, the language naturally supportsletandconst。 Here, we will further elaborate on these new statements and why they are better than othersvarMore suitable.

If you have inadvertently used JavaScript, the next section may be a good way to refresh your memory. If you’re interested in JavaScriptvarAll the quirks of the statement are very familiar, and you may find it easier to skip the front.

1.1 var variable declaration

Traditionally, a variable is declared in JSvarKeyword to complete.

var a = 10

As you may have found, we have just declared aaVariable whose value is10

We can also declare a variable in a function:

function f() {
	var message = "Hello, world!";
	return message;
}

We can also access these same variables in other functions:

function f() {
    var a = 10;
    return function g() {
        var b = a + 1;
        return b;
    };
}
var g = f();
g(); // returns '11'

In the above example, G captures the variable a declared in F. Whenever G is called, the value of a will be associated with the value of a in F.

function f() {
    var a = 1;
    a = 2;
    var b = g();
    a = 3;
    return b;
    function g() {
        return a;
    }
}
f(); // returns '2'

1.2 scope rule

For those who are used to other languages,varDeclarations have some strange scope rules. Take the following example:

function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
        var x = 10;
    }
    return x;
}
f(true); //  Return to '10'
f(false); //  Return 'undefined'

Some readers may doubt this example. The variable x is inifBlock, but we can access it from outside the block. that is becausevarDeclaration can be in itsContained functions, modules, namespaces, or anywhere within the global scopeAccess (all of which we will discuss later), regardless of the contained blocks. Some people call thisvarScopeorFunction scope。 Parameters are also function scopes.

These scope rules can lead to several types of errors. They exacerbate the problem that declaring the same variable multiple times is not an error.

function sumMatrix(matrix: number[][]) {
    var sum = 0;
    for (var i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (var i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }
    return sum;
}

Perhaps for some experienced JavaScript developers, this is easy to find, but internalfor-loopVariables are accidentally overwritteniBecauseiRefers to variables within the same function range. As experienced developers now know, similar bugs can slip through code reviews and become a source of endless frustration.

1.3 quirks of variable capture

Take a moment to guess what the output of the following paragraph is:

for (var i = 0; i < 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 100 * i);
}

For those unfamiliar with it,setTimeoutIt will try to execute a function after a certain number of milliseconds (although it will wait for something else to stop running). The final result is ten lines 10.

Many JavaScript developers are familiar with this behavior, but if you’re surprised, you’re definitely not alone. Most people want the output to be: 1 2 3 4 5 6 7 8 9 10.

Remember the question we mentioned earlier about variable capture? We pass it on tosetTimeoutEach function expression of actually refers to the same function in the same rangei

Let’s take a moment to think about what this means.setTimeoutA function will run after a few milliseconds, but only after the for loop stops executing; When the for loop stops executing, the value of I is 10. Therefore, every time a given function is called, it will print out 10!

A common solution is to useIIFE–A function expression that is called immediately — to capture the I for each iteration.

for (var i = 0; i < 10; i++) {
    //By calling a function with its current value
    //Capture the current state of 'I'
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, 100 * i);
    })(i);
}

This strange looking pattern is actually very common. The I in the parameter list is actually a shadow of the I declared in the for loop, but since we have the same name for them, we don’t need to modify the loop body too much.

1.4 let variable declaration

Now you’ve found outvarThere are some problems, which isletThe reason why the statement was introduced. Except for the keywords used, the let statement is written in the same way as the VaR statement.

let hello = 'hello'

The key difference is not in grammar, but in semantics. Now we need to study it in depth

1.5 block level scope

When a variable is usedletWhen declaring, it uses what some people call lexical scope or chunk scope. And usevarThe declared variables are different,block-scopeThe scope of a block level scoped variable will be leaked to the function it contains, and in its nearest containing block orfor-loopIs invisible outside.

function f(input: boolean) {
    let a = 100;
    if (input) {
        //Reference 'a' is still OK
        let b = a + 1;
        return b;
    }
    //Error: 'B' does not exist here.
    return b;
}

Here, we have two local variables A and B. A’s scope is limited to the body of F, while B’s scope is limited to the block containing the if statement.

staycatchThe variables declared in clause have similar scope rules

try {
    throw "oh no!";
} catch (e) {
    console.log("Oh well.");
}
//Error: 'e' does not exist here.
console.log(e);

Another attribute of block level scope variables is,They cannot be read or written until they are actually declared。 Although these variables “exist” throughout their scope, all points until they are declared are part of their time dead corner. It’s just a complicated statement. You can’tletStatements, and fortunately typescript will let you know that.

a++; //  It is illegal to use 'a' before a declaration.
let a;

Note that you can still capture a block wide variable before declaring it. The only problem is that calling this function before the top note is illegal. If we aim at es2015, the modern runtime will throw an error; However, typescript is now allowed and will not be reported as an error.

function foo() {
    //You can snap to "a".
    return a;
}
//Illegal call to 'foo' before declaring 'a'.
//Runtimes should throw an error here
foo();
let a;

1.6 repeated declarations and projections

aboutvarDeclaration, we mentioned that it doesn’t matter how many times you declare variables, you just get one.

function f(x) {
    var x; var x;
    if (true) {
        var x;
    }
}

In the above example, all statements about X actually refer to the same X, which is completely valid. This often becomes the root of mistakes. Fortunately,letYour statement is not so tolerant.

let x = 10;
let x = 20; //  Error: cannot redeclare 'x' in the same scope.

Variables do not have to be block scoped, and typescript will tell us that there is a problem.

function f(x) {
    let x = 100; //  Error: interfering with parameter declaration
}
function g() {
    let x = 100;
    var x = 100; //  Error: cannot have declaration of 'x' at the same time
}

This is not to say that a block scope variable can never be declared with a function scope variable. Block scope variables simply need to be declared in a significantly different block.

function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }
    return x;
}
f(false, 0); //  Return 0
f(true, 0); //  Return 100

The act of introducing a new name into a more nested scope is called projection. This is a double-edged sword, because it can introduce some errors by itself in case of accidental allusion, and prevent some errors at the same time. For example, imagine what we wrote earlier with let variablessumMatrixFunction:

function sumMatrix(matrix: number[][]) {
    let sum = 0;
    for (let i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (let i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }
    return sum;
}

This version of the loop actually performs summation correctly, because the I of the inner loop will shadow the I of the outer loop.

In order to write clearer code, projection should usually be avoided. Although it may be appropriate to use it in some cases, you should use your best judgment.

1.7 block level scope variable capture

When we first touched withvarWhen declaring the idea of capturing variables, we briefly discussed how variables act once captured. In order to give you a better visual impression, each time a scope is run, it will create a variable “environment”. The environment and its captured variables still exist even after everything in its scope is executed.

function theCityThatAlwaysSleeps() {
    let getCity;
    if (true) {
        let city = "Seattle";
        getCity = function () {
            return city;
        };
    }
    return getCity();
}

Because we have captured it from its environmentcity, so althoughifThe block has been executed and we can still access it.

Think back, before ussetTimeoutIn the example, we finally need to useIIFETo capture the variable state in each iteration of the for loop. actually,What we do is create a new variable environment for the variables we capture。 It’s a bit troublesome, but fortunately, you don’t have to do this in typescript anymore.

When declared as part of a loop, let declarations behave very differently. These declarations do not just introduce a new environment to the loop itself, but create a new scope in each iteration. Because this is what we do in Iife, we can change our previoussetTimeoutFor example, only useletStatement.

for (let i = 0; i < 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 100 * i);
}

It will print as expected: 0 1 2 3 4 5 6 7 8 9

1.8 conststatement

constDeclaration is another way to declare variables

const numLivesForCat = 9;

They are like let declarations, but as their name implies, once they are bound, their values cannot be changed. In other words, they have the same scope rules as lets, but you can’t reassign them.

This should not be confused with the idea that the values they refer to are immutable.

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}; 
//Mistake
kitty = {
    name: "Danielle",
    numLives: numLivesForCat,
};

//The following are correct
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

Unless you take specific measures to avoid it, the internal state of constant variables can still be modified. Fortunately, typescript allows you to specify which members of an object arereadonlyof

1.9 letAndconstcompare

Given that we have two declarations with similar scope semantics, it’s natural to ask ourselves which one to use. Like most broad questions, the answer is: it depends.

According to the principle of least privilege, const should be used for all declarations except those you intend to modify. The reason is that if a variable does not need to be written, others working in the same code base should not be able to write the object automatically. They need to consider whether they really need to re assign to the variable. Using const also makes the code more predictable when inferring data flows.

Use your best judgment and consult with other members of your team if applicable.

Most of the following documents useletStatement.

1.10 deconstruction

Destructuring assignment Syntax is a JavaScript expression. adoptDeconstruction assignment,You can take the attribute / value from the object / array and assign it to other variables.

1.11 array deconstruction

The simplest form of deconstruction is array deconstruction assignment.

let input = [1, 2];
let [first, second] = input;
Console log(first); //  Output 1
Console log(second); //  Output 2

This creates two new variables named first and second. This is equivalent to using indexes, but much more convenient.

first = input[0];
second = input[1];

Deconstruction also applies to declared variables.

//Exchange variable
[first, second] = [second, first];

And it is a function with parameters:

function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f([1, 2]);

You can use grammar...Create a variable for the remaining items in the list

let [first, ...rest] = [1, 2, 3, 4];
Console log(first); //  Output 1
Console log(rest); //  Output [2, 3, 4]

Of course, since this is JavaScript, you can directly ignore the trailing elements you don’t care about:

let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1

1.12 tuple deconstruction

Tuples can be unstructured like arrays; Unstructured variables get the types of corresponding tuple elements:

let tuple: [number, string, boolean] = [7, "hello", true];
let [a, b, c] = tuple; // a: number, b: string, c: boolean

It is an error to deconstruct a tuple beyond the range of its elements:

let [a, b, c, d] = tuple; //  Error, no element at index 3

Like arrays, you can use...Deconstruct the rest of the tuple to get a shorter tuple:

let [a, ...bc] = tuple; // bc: [string, boolean]
let [a, b, c, ...d] = tuple; //  d: [], empty tuple

Either ignore the tail element or ignore other elements:

let [a] = tuple; // a: number
let [, b] = tuple; // b: string

1.13 object deconstruction

You can also deconstruct objects:

let o = { a: "foo", b: 12, c: "bar",};
let { a, b } = o;

This is fromo.aando.bA new variable was created inaandb。 Note that if you don’t needc, you can skip it. Just like array unstructuring, you can assign values without declaration:

({ a, b } = { a: "baz", b: 101 });

Note that we must enclose this statement with parentheses. JavaScript usually parses {as the beginning of a block.

You can use grammar...Create a variable for the remaining items in the object:

let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
  1. Property rename

You can also give attributes different names:

let { a: newName1, b: newName2 } = o;

The grammar here is beginning to get confused. You can puta: newName1pronounce as"a as newName1"。 The direction is from left to right, as you wrote:

let newName1 = o.a;
let newName2 = o.b;

What’s puzzling is that the colon here doesn’t mean type. If you specify a type, you still need to write it after the whole structure is deconstructed.

let { a, b }: { a: string; b: number } = o;
  1. Default value

Default lets you specify a default value in case an attribute is undefined:

function keepWholeObject(wholeObject: { a: string; b?: number }) {
	let { a, b = 1001 } = wholeObject;
}

In this case,b?expressbIs optional, so it may be undefined.keepWholeObjectNow there is onewholeObjectVariables and propertiesaandb, even ifbIs undefined.

1.14 function statement

De structuring also plays a role in function declarations. For simple cases, this is very direct.

type C = { a: string; b?: number };
function f({ a, b }: C): void {
	// ...
}

However, it is common to specify the default value for parameters, and it is difficult to obtain the default value by deconstruction. First, you need to remember to put the mode before the default value.

function f({ a = "", b = 0 } = {}): void {
    // ...
}
f();

Then you need to rememberdestructuredProperty, instead of the main initializer. Remember, the definition of C is B optional.

function f({ a, b = 0 } = { a: "" }): void {
    // ...
}
f({ a: "yes" }); //  Correct, B = 0
f(); //  Correct, default {A: ""}, and then default to B = 0
f({}); //  Error, 'a' is required if you provide a parameter

Use deconstruction carefully. As the previous example shows, anything but the simplest destructor expression can be confusing. This is especially true in deeply nested structures, which can become very difficult to understand without stacking renames, default values, and type comments. Try to keep the structured expression small and simple. You can always write the assignment that will be generated by deconstruction.

1.15 deployment

The expansion operator is the opposite of deconstruction. It allows you to spread an array into another array, or spread an object into another object. for instance:

let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];

This makesbothPlusThe value of is[0, 1, 2, 3, 4, 5]。 Expand createfirstandsecondShallow copy of. They do not change as they unfold.

You can also expand objects.

let defaults = {
    food: "spicy",
    price: "$$",
    ambiance: "noisy"
};
let search = {
    ...defaults,
    food: "rich"
};

Now search is{ food: "rich", price: "$$", ambiance: "noisy" }。 Object expansion is more complex than array expansion. Like array expansion, it goes from left to right, but the result is still an object. This means expandingA later occurrence of a property in an object overrides an earlier occurrence。 Therefore, if we modify the previous example, expand at the end:

let defaults = {
    food: "spicy",
    price: "$$",
    ambiance: "noisy"
};
let search = {
    food: "rich",
    ...defaults
};

Then, the food attribute in defaults overridesfood: "rich", this is not what we want in this situation.

Object propagation also has some other surprising limitations. First, it includes only the enumerable properties of an object itself. Basically, this means that when you propagate an instance of an object, you willLost method

class C {
    p = 12;
    m() {}
}
let c = new C();
let clone = {
    ...c
};
clone. p; //  correct
clone. m(); //  Wrong!

Typescript compiler does not allow type arguments to be expanded from generic functions. This feature is expected to appear in future language versions.

Typescript learning advanced Chapter 2: type inference

In TS, there are several places where type inference is used to provide type information without explicit type annotation. For example, in this Code:

//let x: number
let x = 3

xThe type of variable is inferred asnumber。 This inference takes place inInitialize variables and membersSet parameter defaultsandDetermine function return typeTime.

In most cases, type inference issay without mincing wordsof In the following sections, we will explore some nuances of type inference.

2.1 best public type

When type inference is made from several expressions, the types of these expressions are used to calculate a “best common type”. for instance:

// let x: (number | null)[]
let x = [0, 1, null];

To infer that in the above examplexWe must consider the type of each array element. Here we get the choice of two array types:numberandnull。 The best common type algorithm considers each candidate type, andA type compatible with all other candidate types was selected

Because the best common type must be selected from the provided candidate types, in some cases, types have a common structure, but no type is a supertype of all candidate types. for instance:

// let zoo: (Rhino | Elephant | Snake)[]
let zoo = [new Rhino(), new Elephant(), new Snake()];

Ideally, we might want tozooInferred asAnimal[]But because there is no strict meaning in the arrayAnimalType, so we don’t infer the type of array elements. To correct this, types are explicitly provided when no one type is a supertype of all other candidate types.

// let zoo: Animal[]t
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

When the best common type is not found, the inference is the joint array type,(Rhino | Elephant | Snake)[]

2.2 context type

In some cases of TS, type reasoning also plays a role in the “other direction”. This is called “context typing”. Context typing occurs when the type of an expression is implied by its position. For example:

window.onmousedown = function (mouseEvent) {
    console.log(mouseEvent.button);
    Console log(mouseEvent.kangaroo); //  Ⓧ The 'kangaroo' attribute does not exist on the 'mouseevent' type.
};

Here, the typescript type checker useswindow.onmousedownThe type of the function expression on the right side of the assignment is inferred from the type of the function. When it does, it can infermouseEventThe type of the parameter, which does contain aButtonButton attribute, but does not containkangarooKangaroo property.

The reason for this iswindowAlready declared in its typeonmousedown

//The declaration has a global variable named 'window'
declare var window: Window & typeof globalThis;

//This is declared (simplified version).
interface Window extends GlobalEventHandlers {
    // ...
}

//Many known handler events are defined
interface GlobalEventHandlers {
    onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null;
    // ...
}

Typescript is smart enough to infer types in other situations:

window.onscroll = function (uiEvent) {
    //Ⓧ Property 'button' does not exist on type 'event'.
    console.log(uiEvent.button);
};

Based on the above functions are assigned toWindow.onscrollI know the truthuiEventIt’s aUIEvent, not like the previous exampleMouseEventUIEventObject does not contain button properties, so typescript throws an error.

If the function is not in the position of the context type, the parameter of the function will contain the type implicitlyanyAnd will not issue errors (unless you usenoImplicitAnyOptions).

const handler = function (uiEvent) {
	console.log(uiEvent.button); //

We can also explicitly provide type information to the parameters of the function to override the type of any context.

window.onscroll = function (uiEvent: any) {
	console.log(uiEvent.button); //

However, this code will recordundefinedBecause uievent does not have a property named button.

Context typing is applicable in many cases. Common situations includeParameters of function callRight side of assignmentType Asserts objectandarray literal Members of, andReturn statement。 The context type is also a candidate for the best common type. for instance:

function createZoo(): Animal[] {
	return [new Rhino(), new Elephant(), new Snake()];
}

In this example, the best common type has a set of four candidates.Animal , Rhino , ElephantandSnake。 Among them,AnimalCan be selected by the best common type algorithm.

Typescript learning advanced Chapter 3: Enumeration

EnumsIs one of the few features of typescript. It is not a type level extension of JavaScript.

Enumeration allows developers to define a set of named constants。 Using enumerations makes it easier to record intentions or create a different set of situations. Typescript providesnumberandcharacter stringEnumeration of.

3.1 numerical enumeration

Let’s start with numerical enumeration. If you come from other languages, you may be more familiar with it. An enumeration can be usedenumKeywords.

enum Direction {
    Up = 1,
    Down,
    Left,
    Right,
}

Above, we have a numerical enumeration, in whichUpIs initialized to 1, and all the following members are automatically incremented from this point. let me put it another way,Direction.UpThe value of is 1,DownIt’s 2,LeftIt’s 3,RightIt’s 4.

If we like, we can completely eliminate the initializer:

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

Here, the value of up is 0, down is 1, and so on. suchAuto incrementYour behavior is possible for usDo not care about the member value itself, butCare about how each value differs from other values in the same enumerationVery useful.

Using enumeration is simple: you only need to access any member as a property of the enumeration itself and declare the type with the name of the enumeration:

enum UserResponse {
    No = 0,
    Yes = 1,
}
function respond(recipient: string, message: UserResponse): void {
    // ...
}
respond("Princess Caroline", UserResponse.Yes);

Numeric enumerations can be mixed in calculation and constant members (see below). In short,Enumerations without initializers either need to be placed firstEither it must be placed after a numeric enumeration initialized with numeric constants or other constant enumeration members。 In other words, the following situations are not allowed:

enum E {
    A = getSomeValue(),
    B,
    //Ⓧ Enum members must have initializers.
}

3.2 string enumeration

String enumeration is a similar concept, but there are some subtle runtime differences, as described below. In a string enumeration,Each member must be constant initialized with a string prefix or another string enumeration member

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

Although string enumerations do not automatically increment, one advantage of string enumerations is that they work well“Serialization”。 In other words, if you have to read the runtime value of a numeric enumeration during debugging, the value is oftenOpaqueYes — it doesn’t convey any useful meaning in itself (reverse mapping is often possible). String enumeration allows you to give a meaningful and readable value when the code runs, regardless of the name of the enumeration member itself.

3.3 heterogeneous enumeration

Technically, enumerations can be mixed with string and numeric members, but it’s not clear why you want to do this:

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

Unless you really want to take advantage of JavaScript’s runtime behavior in a clever way, it’s not recommended.

3.4 calculated and constant members

Each enumeration member has a value associated with it, which can be a constant or a calculated value. An enumeration member is considered a constant if:

  • It is the first member in the enumeration and has no initializer, in which case it is assigned0
// E.X is constant:
enum E { X,}
  • It does not have an initializer, and the previous enumeration member is aDigital constant。 In this case, the value of the current enumeration member will beThe value of the previous enumeration member is incremented by 1
//All enumeration members in 'E1' and 'E2' are constants.
enum E1 { X, Y, Z,}
enum E2 { A = 1, B, C,}

Enumeration members are initialized with a constant enumeration expression. Constant enumeration expressions are a subset of typescript expressions that can be fully evaluated at compile time. An expression is a constant enumeration expression. If it is:

  1. The literal meaning of enumeration expression (basically a string literal or a numeric literal);
  2. A reference to a previously defined constant enumeration member (can be from a different enumeration);
  3. A constant enumeration expression in parentheses;
  4. Applied to constant enumeration expressions+ , - , ~One of the single operators;
  5. +, - , * , / , % , << , >> , & , | , ^Binary operator with constant enumeration expression as operand.

If the constant enumeration expression is evaluated asNaNorInfinity, this is a compile time error.

In all other cases, enumeration members are considered computed.

enum FileAccess {
    //Constant member
    None,
    Read = 1 << 1,
    Write = 1 << 2,
    ReadWrite = Read | Write,
    //Calculation member
    G = "123".length,
}

3.5 joint enumeration and enumeration member types

A special subset of constant enumeration members is not evaluated:Literal enumeration member。 A literal enumeration member is a constant enumeration member without an initialization value, or its value is initialized to:

  • Any string (for example:"foo" , "bar" , "baz"
  • Any numeric prefix (e.g. 1100)
  • Singular minus sign applied to any number literal (e.g. – 1, – 100)

When all members in an enumeration haveLiteral value of enumerationSome special semantics will play a role.

First, enumeration members also become types. For example, we can say that some members can only have the value of one enumerated member:

enum ShapeKind {
    Circle,
    Square,
}
interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}
interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}
let c: Circle = {
    kind: ShapeKind.Square,
    //Ⓧ Type 'shapekind Square 'cannot be assigned to type' shapekind Circle'
    radius: 100,
}

Another change is that the enumeration type itself effectively becomes the Federation of each enumeration member. Through joint enumeration, the type system can take advantage of the fact thatIt knows the exact set of values that exist in the enumeration itself。 Because of this, typescript can catch errors that we may mistakenly compare values. for instance:

enum E {
    Foo,
    Bar,
}
function f(x: E) {
    if (x !== E.Foo || x !== E.Bar) {
        //Ⓧ This condition will always return 'true' because the types of 'e.foo' and 'e.bar' do not coincide.
        //...
    }
}

In this example, we first checkxIs it notE.Foo。 If this check is successful, then our||Will be short circuited,ifThe body of the statement will run. However, if the check is not successful, then x can only beE.FooSo let’s see if it’s equal toE.BarIt doesn’t make sense.

3.6 runtime enumeration

Enumeration exists at runtimeReal object。 For example, the following enumeration

enum E {
    X,
    Y,
    Z,
}

Can actually be passed to the function:

enum E {
    X,
    Y,
    Z,
}
function f(obj: { X: number }) {
    return obj.X;
}

//It works because 'e' has an attribute called 'x', which is a number.
f(E);

3.7 enumeration at compile time

althoughEnumIs a real object that exists at runtime,keyofKeywords work differently from what you expect from the object. Instead, usekeyof typeofTo get one that will allEnumThe key is represented ascharacter stringType of.

enum LogLevel {
    ERROR,
    WARN,
    INFO,
    DEBUG,
}
/**
*This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;
function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key];
    if (num <= LogLevel.WARN) {
        console.log("Log level key is:", key);
        console.log("Log level value is:", num);
        console.log("Log level message is:", message);
    }
}
printImportant("ERROR", "This is a message");
  • Reverse mapping

exceptCreate an object with a property name for the memberIn addition, members of numeric enumeration can alsoGets the reverse mapping from the enumeration value to the enumeration name。 For example, in this example:

enum Enum {
    A,
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

Typescript compiles it into the following javascript:

"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

In this generated code, an enumeration is compiled into an object, which also stores the forward( name -> value )And reverse( value -> name )Mapping relationship of. References to other enumeration members are always issued as property access and are never inlined.

Keep in mind that string enumeration members will not be reverse mapped at all.

  • constenumeration

In most cases, enumeration is a completely effective solution. However, sometimes the requirements are more stringent. in order toAvoid the cost of extra generated code and extra indirection when accessing enumeration values, you can useconstEnumeration. Constant enumeration is based on our enumerationconstModifier.

const enum Enum {
    A = 1,
    B = A * 2,
}

Constant enumeration can only use constant enumeration expressions. Unlike ordinary enumeration, they are completely deleted during compilation. Constant enumeration members are inlined where they are used. This is possible because constant enumerations cannot have computed members.

const enum Direction {
Up,
Down,
Left,
Right,
}
let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];

In the generated code, it will become:

"use strict";
let directions = [
    0 /* Up */ ,
    1 /* Down */ ,
    2 /* Left */ ,
    3 /* Right */ ,
];

3.8 environment enumeration

Environment enumeration is a shape used to describe existing enumeration types.

declare enum Enum {
A = 1,
B,
C = 2,
}

An important difference between environment enumerations and non environment enumerations is that in conventional enumerations, if the preceding enumeration member is considered a constant, then a member without an initializer will be considered a constant. Instead, oneEnvironment without initializer(andExtraordinary quantity)Enumeration members are always considered computed.

3.9 objects and enumerations

In modern typescript, you may not need an enumeration because an object constant is enough:

const enum EDirection {
    Up,
    Down,
    Left,
    Right,
}
const ODirection = {
    Up: 0,
    Down: 1,
    Left: 2,
    Right: 3,
} as const;

// (enum member) EDirection.Up = 0
EDirection.Up;

// (property) Up: 0
ODirection.Up;

//Enumeration as a parameter
function walk(dir: EDirection) {}

//It requires an extra row to pull out the value
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}

walk(EDirection.Left);
run(ODirection.Right);

Compared with typescript enumeration, the biggest reason for supporting this format is that it keeps your code base consistent with the state of JavaScript,when/ifEnumerations are added to JavaScript, so you can move to additional syntax.

Typescript learning advanced Chapter 4: public types

Typescript provides several practical types to facilitate common type conversions. These utilities are available globally. The“

4.1 Partial

Build a type that willTypeAll properties of are set to optional. This tool will return a type that represents all subsets of a given type.

example:

interface Todo {
    title: string;
    description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial) {
    return { ...todo, ...fieldsToUpdate };
}

const todo1:Todo = {
    title: "organize desk",
    description: "clear clutter",
};

//Because all properties can be set as optional, and if optional is not set, it will be undefined, so take this to test
const todo2:Partial = updateTodo(todo1, {
    description: undefined,
})

4.2 Required

Build aTypeThe type composed of all the attributes of. It is required to be set. AndPartialcontrary:

interface Props {
    a?: number;
    b?: string;
}
const obj: Props = { a: 5 };
//Error, type '{A: number;}' Attribute 'B' is missing from but is required in type 'required'
const obj2: Required = { a: 5 };

4.3 Readonly

Build a type,TypeAll properties of are set toreadonlyThis means that the properties of the built type cannot be reset.

interface Todo {
title: string;
}
const todo: Readonly = {
title: "Delete inactive users",
};

// error
todo.title = "Hello";

This tool is useful for assignment expressions that will fail at run time (i.e. when trying to reassign aFreeze objectProperty of the.

function freeze(obj: Type): Readonly;

4.4 Record

Build an object type. Its attribute key is keys and its attribute value is type. This tool can be used to map attributes of one type to another:

interface CatInfo {
    age: number;
    breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
const cats: Record = {
    miffy: { age: 10, breed: "Persian" },
    boris: { age: 5, breed: "Maine Coon" },
    mordred: { age: 16, breed: "British Shorthair" },
};

// const cats: Record
console.log(cats.boris) // { age: 5, breed: 'Maine Coon' }

4.5 Pick

Construct a type by selecting the Attribute Collection keys (attribute name or union of attribute names) from the type:

interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Pick;
const todo: TodoPreview = {
    title: "Clean room",
    completed: false,
};

// const todo: TodoPreview
todo;

4.6 Omit

Through fromTypeSelect all properties in the and delete themKeys(property name or union of property names) to construct a type.

interface Todo {
    title: string;
    description: string;
    completed: boolean;
    createdAt: number;
}
type TodoPreview = Omit;
const todo: TodoPreview = {
    title: "Clean room",
    completed: false,
    createdAt: 1615544252770,
};
// const todo: TodoPreview
todo;

type TodoInfo = Omit;
const todoInfo: TodoInfo = {
    title: "Pick up kids",
    description: "Kindergarten closes at 5pm",
};
// const todoInfo: TodoInfo
todoInfo;

4.7 Exclude

Through fromTypeExclude all that can be assigned toExcludedUnionTo construct a type.

// type T0 = "b" | "c"
type T0 = Exclude;

// type T1 = "c"
type T1 = Exclude;

// type T2 = string | number
type T2 = Exclude void), Function>;

4.8 Extract

Through fromTypeCan be allocated toUnionAllunionMember to construct a type.

// type T0 = "a"
type T0 = Extract

// type T1 = () => void
type T1 = Extract void), Function>

4.9 NonNullable

Construct a type by excluding null and undefined from the type.

// type T0 = string | number
type T0 = NonNullable;

// type T1 = string[]
type T1 = NonNullable;

4.10 Parameters

From a function typeTypeofparameterBuild a tuple type from the type used in.

declare function f1(arg: { a: number; b: string }): void;
// type T0 = []
type T0 = Parameters string>;
// type T1 = [s: string]
type T1 = Parameters void>;
// type T2 = [arg: unknown]
type T2 = Parameters<(arg: T) => T>;
/*
type T3 = [arg: {
a: number;
b: string;
}]
*/
type T3 = Parameters;
// type T4 = unknown[]
type T4 = Parameters;
// type T5 = never
type T5 = Parameters;
// type T6 = never
type T6 = Parameters;
// type T7 = never
type T7 = Parameters;

4.11 ConstructorParameters

fromConstructorConstruct atupleorarrayType. It produces a tuple type with all parameter types (if any)TypeIs not a function; otherwiseneverType).

// type T0 = [message?: string]
type T0 = ConstructorParameters;

// type T1 = string[]
type T1 = ConstructorParameters;

// type T2 = [pattern: string | RegExp, flags?: string]
type T2 = ConstructorParameters;

// type T3 = unknown[]
type T3 = ConstructorParameters;

// type T4 = never
type T4 = ConstructorParameters;

4.12 ReturnType

Build a functionTypeType consisting of the return type of the. Yes if genericunknown

declare function f1(): { a: number; b: string };
// type T0 = string
type T0 = ReturnType string>;
// type T1 = void
type T1 = ReturnType void>;
// type T2 = unknown
type T2 = ReturnType<() => T>;
// type T3 = number[]
type T3 = ReturnType<() => T>;

/*
type T4 = {
    a: number;
    b: string;
}
*/
type T4 = ReturnType;
// type T5 = any
type T5 = ReturnType;
// type T6 = never
type T6 = ReturnType;
//Type T7 = any error
type T7 = ReturnType;
//Type T8 = any error
type T8 = ReturnType

4.13 InstanceType

Build aTypeinInstance of constructorType consists of the type.

class C {
	x = 0;
	y = 0;
}
// type T0 = C
type T0 = InstanceType;
// type T1 = any
type T1 = InstanceType;
// type T2 = never
type T2 = InstanceType;
// type T3 = any
type T3 = InstanceType;
// type T4 = any
type T4 = InstanceType;

4.14 ThisParameterType

Extract a function typethisParameter type, if the function type does notthisParameter; otherwiseunknown

function toHex(this: Number) {
	return this.toString(16);
}
// n: number
function numberToString(n: ThisParameterType) {
	return toHex.apply(n);
}

4.15 OmitThisParameter

removeTypeofthisParameters. IfTypeNot explicitly statedthisParameter, the result is justType。 Otherwise, nonethisThe new function type of the parameter will be fromTypeestablish. Generics are erased and only the last overloaded signature is propagated to the new function type.

function toHex(this: Number) {
	return this.toString(16);
}

const fiveToHex: OmitThisParameter = toHex.bind(5);
console.log(fiveToHex());

4.16 ThisType

This tool does not return a converted type. contrary,As a contextthisTag of type。 be careful,Must be enablednoImplicitThisFlag to use this tool.

The & in TS type indicates the cross type, which is mainly used forCombine existing object types

type ObjectDescriptor = {
    data?: D;
    methods?:  M & ThisType; //  The 'this' type in the method is D & M
};

function makeObject(desc: ObjectDescriptor): D & M {
    let data: object = desc.data || {};
    let methods: object = desc.methods || {};
    return { ...data, ...methods } as D & M;
}
let obj = makeObject({
    data: { x: 0, y: 0 },
    methods: {
        moveBy(dx: number, dy: number) {
            this.x += dx;
            this.y += dy;
        },
    },
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

In the above example,makeObjectOf the parametersmethodsObject has aThisTypeThe context type of the method objectthisThe type of is{ x: number, y: number } & { moveBy(dx: number, dy: number): number }。 Notice how the type of the methods attribute is the source of this type in both the reasoning target and the method. Thistype tag interface is only in lib d. An empty interface declared in TS. This interface behaves like any empty interface except that it is recognized in the literal context type of the object.

4.17 string operation type

Uppercase
Lowercase
Capitalize
Uncapitalize

Typescript includes a set of types that can be used for string operations in a type system. You canTemplate Literal TypesThe usage of these tools can be found in the documentation.

Typescript learning advanced Chapter 5: Symbols

fromECMAScript 2015(ES6)Start,symbolIs a primitive data type, likenumberandstringSame.

symbolValue is by callingSymbolConstructor.

let sym1 = Symbol();
let sym2 = Symbol("key"); //  Optional string key

Symbols are immutable and unique.

let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; //  False, symbols are unique

Like a string,SymbolsCan be used as a key for object properties.

const sym = Symbol();
let obj = {
    [sym]: "value",
};
console.log(obj[sym]); // "value"

SymbolsIt can also be combined with a calculated attribute declaration to declareObject propertiesandClass member

const getClassNameSymbol = Symbol();
class C {
    [getClassNameSymbol]() {
        return "C";
    }
}
let c = new C();
let className = c[getClassNameSymbol](); // "C"

5.1 unique symbol

In order to be able tosymbolsAs the only literal symbol, a special type is providedunique symbolunique symbolyessymbolA subtype of, which is only calledSymbol()orSymbol.for()orExplicit type notesGenerated when. This type is only allowed inConstant declaration andRead only static propertiesIn order to reference a specific unique symbol, you must usetypeofOperator. Each reference to a unique symbol means a completely unique identity associated with a given declaration.

declare const sym1: unique symbol;
//Sym2 can only be a constant reference.
let sym2: unique symbol = Symbol();
//Ⓧ Variables of type 'unique symbol' must be of type 'const'.

//Correct operation -- refers to a unique symbol, but its identity is associated with 'SYM1'.
let sym3: typeof sym1 = sym1;

//It's also right
class C {
    static readonly StaticSymbol: unique symbol = Symbol();
}

Because everyunique symbolHave a completely independent identity, not twounique symbolThe type is OKMutual distributionorcompareof

const sym2 = Symbol();
const sym3 = Symbol();

//This condition will always return 'false' because the types of 'typeof sym2' and 'typeof sym3' do not coincide.
if (sym2 === sym3) {
	// ...
}

5.2 well known symbols

In addition to user-definedsymbolsIn addition, there is a famous built-insymbols。 Built in symbols are used to represent internal language behavior.

Here is a famoussymbolsList:

5.2.1 Symbol.hasInstance

OneDetermine constructor object, whether to identify an object as one of the instances of the constructor. frominstanceofSemantic call of operator.

5.2.2 Symbol.isConcatSpreadable

A Boolean value indicating that an object should beArray.prototype.concatTile to its array elements.

5.2.3 Symbol.iterator

Method that returns the default iterator of an object. coverfor-ofCalled by the semantics of the statement.

5.2.4 Symbol.match

A regular expression method that matches the regular expression of a string. fromString.prototype.matchMethod call.

5.2.5 Symbol.replace

A regular expression method used to replace matching substrings in a string. fromString.prototype.replaceMethod call.

5.2.6 Symbol.search

A regular expression method that returns the index in the string that matches the regular expression. fromString.prototype.searchMethod call.

5.2.7 Symbol.species

The property of a function value is the constructor used to create a derived object.

5.2.8 Symbol.split

A regular expression method that splits a string at an index that matches the regular expression. fromString.prototype.splitMethod call.

5.2.9 Symbol.toPrimitive

A method of converting an object to a corresponding primitive value. fromToPrimitiveAbstract operation call.

5.2.10 Symbol.toStringTag

A string value used to create the default string description of an object. By built-in methodObject.prototype.toStringCall.

5.2.11 Symbol.unscopables

An object’s own attribute name is an attribute name that is excluded from the ‘with’ environment binding of the related object.

Typescript learning advanced Chapter 6: type compatibility

Type compatibility in typescript is based onStructure subtypeof Structural typing is a method based entirely on the type relationship of its members.

This is different from the nominal type. Consider the following code:

interface Pet {
    name: string;
}

class Dog {
    name: string;
}

let pet: Pet;
//Correct, because structured types
pet = new Dog();

In likeC#orJavaIn such a nominally typed language, the corresponding code will be an error,becauseDogClass does not explicitly describe itself asPetImplementer of interface

TypescriptStructure type systemIt is designed according to the typical writing method of JavaScript code. Because JavaScriptAnonymous objects are widely used, such asFunction expressionandObject Literal , useStructure type systemIt is much more natural to represent various relationships in JavaScript libraries instead of naming type systems.

6.1 description of soundness

Typescript’s type system allows certain operations that cannot be known at compile time to be safe. When a type system has this property, it is called unsound. We have carefully considered where typescript allows unhealthy behavior, and in this document, we will explain where these occur and the motivational scenarios behind them.

6.2 starting

The basic rule of the structure type system of typescript is ifyAt least withxThe same member, thenxAndyIs compatible. wwww

interface Pet {
    name: string;
}
let pet: Pet;
//Dog's inference type is {Name: string; owner: string;}
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
pet = dog

To checkdogCan be assigned topet, compiler checkpetFor each attribute to finddogThe corresponding compatibility attribute in. under these circumstances,dogThere must be one namednameIs a string. It has, so assignment is allowed.

The same assignment rules are used when checking function call parameters.

interface Pet {
    name: string;
}
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
function greet(pet: Pet) {
    console.log("Hello, " + pet.name);
}
greet(dog); //  correct

Please note that,dogThere is an extraownerProperty, but this does not produce an error. When checking compatibility, only the target type is considered (in this case:Pet)Members of.

The comparison process isrecursionExplore the types of each member and child member.

6.3 comparing two functions

Although it is relatively direct to compare primitive types with object types, the problem of what functions should be considered compatible is a little complicated. Let’s start with the basic examples of two functions, which differ only in the parameter list:

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; //  correct
x = y; //  error

To checkxCan I assign toyFirst, let’s look at the parameter list.xEach parameter inyThere must be a corresponding parameter with compatible type in. Note that the names of parameters are not considered, only their types are considered. under these circumstances,xEach parameter inyThere is a corresponding compatible parameter in, so this assignment is allowed.

The second assignment is an error becauseyThere is onexThere is no necessary second parameter, so this assignment is not allowed.

You may wonder why we allow things like the one in the exampley = xThen “discard” the parameter. This assignment is allowed because it is common in JavaScript to ignore additional function parameters. For example,Array#forEachThree parameters are provided for the callback function: array element, its index and containing array. Nevertheless, it is useful to provide a callback that uses only the first parameter:

let items = [1, 2, 3];
//Do not force these additional parameters
items.forEach((item, index, array) => console.log(item));
//There should be no problem!
items.forEach((item) => console.log(item));

Now let’s look at how to handle return types, using two functions that differ only by return type:

let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });
x = y; //  correct
y = x; //  Error because x () is missing a location attribute

Type system mandatoryReturn type of source functionyesReturn type of target typeOne ofSubtype

6.4 double difference of function parameters

enum EventType {
    Mouse,
    Keyboard,
}
interface Event {
    timestamp: number;
}
interface MyMouseEvent extends Event {
    x: number;
    y: number;
}
interface MyKeyEvent extends Event {
    keyCode: number;
}
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
    /* ... */
}
//Not sound, but useful and common
listenEvent(EventType.Mouse, (e: MyMouseEvent) => console.log(e.x + "," + e.y));
//When soundness exists, it is an undesirable choice
listenEvent(EventType.Mouse, (e: Event) =>
            console.log((e as MyMouseEvent).x + "," + (e as MyMouseEvent).y)
           );
listenEvent(EventType.Mouse, ((e: MyMouseEvent) =>
                              console.log(e.x + "," + e.y)) as (e: Event) => void);
//Still not allowed (explicit error). Enforce type safety for completely incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e));

When this happens, you can let typescript pass the compiler flagstrictFunctionTypesAn error was raised.

6.5 optional parameters and other parameters

When comparing the compatibility of functions,Optional parametersandRequired parametersIs interchangeable. The additional optional parameters of the source type are not errors, and the optional parameters of the target type have no corresponding parameters in the source type.

When a function has aResidual parametersWhen, it is regarded asAn infinite series of optional parameters

From the perspective of type system, this is not sound, but from the perspective of runtime, the concept of optional parameters is generally not well strengthened because they are passed in this positionundefinedThe parameters of are equivalent to most functions.

The motivating example is a common pattern of a function that accepts a callback and calls it with some predictable (to the programmer) but unknown (to the type system) number of parameters.

function invokeLater(args: any[], callback: (...args: any[]) => void) {
    /* ...  Call callback with 'args'*/
}
//Unsound - invokelater "may" provide any number of parameters
invokeLater([1, 2], (x, y) => console.log(x + ", " + y));
//What's puzzling is that (x and y are actually needed) and they can't be found
invokeLater([1, 2], (x?, y?) => console.log(x + ", " + y));

6.6 functions with overloads

When a function has overloads, each overload in the source type must be matched by a compatible signature on the target type. This ensures that the target function can be called in all the same cases as the source function.

6.7 enumeration

Enumerations are compatible with numbers, and numbers are compatible with enumerations。 Enumeration values from different enumeration types are considered incompatible. for instance:

enum Status {
    Ready,
    Waiting,
}

enum Color {
    Red,
    Blue,
    Green,
}

let status = Status.Ready;
status = Color. Green; //  error

Category 6.8

Classes work like object literal types and interfaces, with one exception: they have both static and instance types. When comparing two objects of a class type, only the members of the instance are compared.Static members and constructors do not affect compatibility

class Animal {
    feet: number;
    constructor(name: string, numFeet: number) {}
}
class Size {
    feet: number;
    constructor(numFeet: number) {}
}
let a: Animal;
let s: Size;
a = s; //  correct
s = a; //  correct

6.9 private and protected members in classes

Private and protected members in a class affect their compatibility. When an instance of a class is checked for compatibility, if the target type contains a private member, the source type must also contain a private member from the same class. Similarly, this applies to instances of protected members. thisAllows a class to be assignment compatible with its superclass, but it is not allowed to be compatible with the assignment of classes from different inheritance levels, otherwise it will have the same shape.

6.10 generics

Because typescript is a structured type system, type parameters only affect the result type when consumed as part of the member type. for instance:

interface Empty {}
let x: Empty;
let y: Empty;
x = y; //  Right, because y conforms to the structure of X

on top,xandyAre compatible because their structures do not use type parameters in a differentiated manner. By givingEmptyAdding a member to change this example shows how this works.

interface NotEmpty {
data: T;
}
let x: NotEmpty;
let y: NotEmpty;
x = y; //  Error because X and y are incompatible

In this way, a generic type that specifies type parameters is like a non generic type.

aboutGeneric type with no type parameter specified, yesSpecify any to replace all unspecified type parameters。 thenThe generated types are checked for compatibility, as in the case of non generics.

for instance:

let identity = function (x: T): T {
    // ...
};
let reverse = function (y: U): U {
    // ...
};
identity = reverse; //  Correct, because (X: any) = > any matches (Y: any) = > any

6.11 subtypes and assignments

So far, we have used “compatibility”, which is not a term defined in the language specification. In typescript, there are two kinds of compatibility:Subtypeandassignment。 The only difference is that assignment extends the compatibility of subtypes and allows assignment toany, and assigned to a with a corresponding valueenum

Depending on the situation, one of these two compatibility mechanisms is used in different parts of the language. In practical application, type compatibility is determined byassignment compatible Decided, even inimplementsandextendsClause.

6.12 any ,unknown ,object ,void ,undefined ,null, andneverDistributability

The following table summarizes the allocability between some abstract types. The row indicates what each type can be assigned to, and the list indicates what can be assigned to them. “←” indicates that only when it is closedstrictNullChecksIs a compatible combination

any unknown object void undefined null never
any
unknown
object
void
undefined
null
never
  • Everything can be assigned to yourself.
  • anyandunknownIn terms of distributable content, it is the same, but the difference isunknownCan’t be assigned to anything exceptany
  • unknownandneverIt’s like the opposite of each other. Everything can be assigned tounknown , neverCan be allocated to everything. Nothing can be assigned toneverunknownCan’t be assigned to anything (exceptany )。
  • voidCannot be assigned to anything, with the following exceptions:anyunknownneverundefinedandnull(if)strictNullChecksIt is closed (see table for details).
  • WhenstrictNullChecksWhen closed,nullandundefinedAndneverSimilar: it can be assigned to most types, and most types cannot be assigned to them. They can assign values to each other.
  • WhenstrictNullChecksWhen on,nullandundefinedYour behavior is more likevoid: exceptanyunknownneverandvoidCan’t assign to anything except(undefinedCan always be assigned tovoid )。

Typescript learning advanced Chapter 7: iterator and generation

7.1 traversal

If an object hasSymbol.iteratorProperty, it is considered to be iterative. Some built-in types, such asArrayMapSetStringInt32ArrayUint32ArrayWait, they ‘ve already been implementedSymbol.iteratorProperties. On objectSymbol.iteratoThe R function returns a list of values to iterate over.

7.1.1 IterableInterface

IterableIs a type that we can use if we want to receive the iteratable types listed above. Here is an example:

//The parameter passed in must be an iteratable type
function toArray(xs: Iterable): X[] {
	return [...xs]
}

7.1.2 for ... ofstatement

for... ofLoop over an iteratable object and call theSymbol.iteratorProperties. Here is a simple example of an arrayfor... ofCycle.

let someArray = [1, "string", false];
for (let entry of someArray) {
	console.log(entry); // 1, "string", false
}

7.1.3 for ... ofAndfor ... instatement

for...ofandfor...inStatements are iterated on the list; But the value of iteration is different,for...inReturns the of the iterated objectKey value list, andfor...ofReturns the of the iterated objectList of numeric attribute values。 Here is an example of this difference:

let list = [4, 5, 6];
for (let i in list) {
    console.log(i); // "0", "1", "2",
}
for (let i of list) {
    console.log(i); // 4, 5, 6
}

Another difference isfor...inOperate on any object; It acts as a way to check the properties on the object. On the other hand,for...ofMainly interested in the values of iteratable objects. imageMapandSetSuch built-in objects are implementedSymbol.iteratorProperty to allow access to stored values.

//Iterable in set
let pets = new Set(["Cat", "Dog", "Hamster"]);
for (let pet in pets) {
    Console log(pet); //  Nothing output
}
for (let pet of pets) {
    console.log(pet); // "Cat", "Dog", "Hamster"
}
//Iterable in map
let nums = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
])
for (let num in nums) {
    Console Log (Num) // nothing is output
}

for (let num of nums) {
    console.log(num) //[ 1, 'one' ]  [ 2, 'two' ]  [ 3, 'three' ]
}

7.2 code generation

7.2.1 generate target Es5 and Es3

When targeting Es5 or Es3 compatible engines, iterators are only allowed inArrayUsed on values of type.

Use on non array valuesfor...ofA loop is an error, even if these non array values are implementedSymbol.iteratorProperties.

For example, the compiler will befor...The loop generates a simpleforCycle.

let numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}

Will be born:

var numbers = [1, 2, 3];
for (var _i = 0; _i < numbers.length; _i++) {
    var num = numbers[_i];
    console.log(num);
}

7.2.2 ECMAScript 2015 (ES6) and later

When targeting ecmascipt 2015 compatible engines, the compiler generatesfor...ofLoop for the built-in iterator in the engine.

Typescript learning advanced Chapter 8: decorators

8.1 introduction

With the introduction of classes in typescript and ES6, there are some scenarios that require additional functionality to supportAnnotate or modify classes and class members。 The decorator provides aAdd comments and metaprogramming syntax for class declarations and membersMethod. Decorator is the second stage recommendation of JavaScript and is provided as an experimental function of typescript.

Note: the decorator is an experimental feature that may change in future versions.

To enable experimental support for decorators, you must be on the command line or in thetsconfig.jsonEnabled inexperimentalDecoratorsCompiler options.

  • Command line on
tsc --target ES5 --experimentalDecorators
  • tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

8.2 decorator

Decorators are special declarations that can be attached to classesstatementmethodAccessorProperty or parameterCome on. Use of decorator@expressionIn whichexpressionMust be evaluated as a function,This function will be called at runtime with information about the decorated declaration

For example, for decorators@sealed, we cansealedThe function of is written as follows:

function sealed(target) {
	//Do something about "target"
}

8.3 decorator factory

If we want to customize how decorators are applied to declarations, we can write a decorator factory. Decorator factory is a simple function that returns the expression that will be called by the decorator at run time.

We can write a decorator factory in the following way:

function color(value: string) {
    //This is a decorator factory. It sets
    //Decorator function returned
    return function (target) {
        //This is the decorator
        //Do something with "target" and "value"
    };
}

8.4 composition of decorator

Multiple decorators can be applied to a declaration, such as in one line:

@f @g x

Multiline syntax:

@f
@g
x

When multiple decorators apply to a declaration, their evaluation is similar to that in mathematicsFunction combination。 In this mode, when combining functions f and G, the resulting combination(f(g))(x)Equivalent tof(g(x))

Therefore, when evaluating multiple decorators of a declaration in typescript, the following steps are performed:

  1. The expression of each decorator is evaluated from top to bottom.
  2. Then call the result as a function from bottom to top.

If we use a decorator factory, we can observe this evaluation sequence through the following example:

function first() {
    console.log("first(): factory evaluated");
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("first(): called");
    };
}

function second() {
    console.log("second(): factory evaluated");
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("second(): called");
    };
}

class ExampleClass {
    @first()
    @second()
    method() {}
}

This will print this output to the console:

first(): factory evaluated
second(): factory evaluated
second(): called
first(): called

8.5 decorator evaluation

For decorators that apply to various declarations within a class, there is a clear order:

  1. For each instance member, first the parameter decorator, then the method, accessor, or property decorator.
  2. For each static member, first the parameter decorator, then the method, accessor, or property decorator.
  3. Parameter decorators are applied to constructors.
  4. Class decorators apply to classes.

Class 8.6 decorators

The class decorator is declared just before the class declaration. Class decorators are applied to class constructors and can be used to observe, modify, or replace class definitions. Class decorators cannot be used in declaration files or in any other environment (such asdeclareClass).

The expression of the class decorator will be called as a function at run time, and the constructor of the decorated class is its only parameter.

If the class decorator returns a value, it replaces the class declaration with the supplied constructor.

Note: if you choose to return a new constructor, you must pay attention to maintaining the original prototype. The logic of applying decorators at runtime won’t do this for you.

The following is an example that applies toBugReportClass decorator for class(@sealed)For example.

@sealed
class BugReport {
    type = "report";
    title: string;
    constructor(t: string) {
        this.title = t;
}
}

We can use the following function declaration to define@sealedDecorator

// Object. The seal () method encloses an object, prevents the addition of new properties, and marks all existing properties as non configurable. The value of the current property can be changed as long as it is writable.
function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

When@sealedWhen executed, it will close both the constructor and its prototype, so it will prevent access at run timeBugReport.prototypeOr by definitionBugReportTo add or remove any further functionality to the class (note that the es2015 class is actually just a syntax sugar of a prototype based constructor). This decorator does not prevent class pairsBugReportSubclass.

Next, we have an example of how to override the constructor to set a new default value:

function reportableClassDecorator(constructor: T){
    return class extends constructor {
        reportingURL = "http://www...";
    };
}

@reportableClassDecorator
class BugReport {
    type = "report";
    title: string;
    constructor(t: string) {
        this.title = t;
    }
}
const bug = new BugReport("Needs dark mode");
Console log(bug.title); //  Print "needs dark mode"
Console log(bug.type); //  Print "report"
//Note that the decorator does not change the type of typescript
//Therefore, the type system is agnostic to the new attribute 'reportingurl'.
bug.reportingURL;

Recommended Today

[NOIP2001 Popularization Group] Seek Ranking

Question analysis: The question mentions the pre-order, in-order, and post-order arrangement of trees, so we need to know what these three arrangements mean first. 3 (depth-first) permutations of binary trees: Pre-order, &quot;root left and right&quot;. That is, for each subtree of the binary tree, first visit its root, and then traverse its left and right […]