- Typescript learning advanced Chapter 1: variable declaration
- 1.1 var variable declaration
- 1.2 scope rule
- 1.3 quirks of variable capture
- 1.4 let variable declaration
- 1.5 block level scope
- 1.6 repeated declarations and projections
- 1.7 block level scope variable capture
- 1.8
const
statement - 1.9
let
Andconst
compare - 1.10 deconstruction
- 1.11 array deconstruction
- 1.12 tuple deconstruction
- 1.13 object deconstruction
- 1.14 function statement
- 1.15 deployment
- Typescript learning advanced Chapter 2: type inference
- Typescript learning advanced Chapter 3: Enumeration
- Typescript learning advanced Chapter 4: public types
- Typescript learning advanced Chapter 5: Symbols
- Typescript learning advanced Chapter 6: type compatibility
- 6.1 description of soundness
- 6.2 starting
- 6.3 comparing two functions
- 6.4 double difference of function parameters
- 6.5 optional parameters and other parameters
- 6.6 functions with overloads
- 6.7 enumeration
- Category 6.8
- 6.9 private and protected members in classes
- 6.10 generics
- 6.11 subtypes and assignments
- 6.12
any
,unknown
,object
,void
,undefined
,null
, andnever
Distributability
- Typescript learning advanced Chapter 7: iterator and generation
Typescript learning advanced Chapter 1: variable declaration
let
andconst
Are two relatively new concepts of variable declaration in JavaScript. As we mentioned earlier,let
In some ways withvar
Similar, but allows users to avoid some common “troubles” in JavaScript.
const
yeslet
An extension to prevent reassignment to a variable.
Since typescript is an extension of JavaScript, the language naturally supportslet
andconst
。 Here, we will further elaborate on these new statements and why they are better than othersvar
More suitable.
If you have inadvertently used JavaScript, the next section may be a good way to refresh your memory. If you’re interested in JavaScriptvar
All 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 JSvar
Keyword to complete.
var a = 10
As you may have found, we have just declared aa
Variable 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,var
Declarations 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 inif
Block, but we can access it from outside the block. that is becausevar
Declaration 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 thisvar
ScopeorFunction 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-loop
Variables are accidentally overwritteni
Becausei
Refers 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,setTimeout
It 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 tosetTimeout
Each function expression of actually refers to the same function in the same rangei
。
Let’s take a moment to think about what this means.setTimeout
A 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 outvar
There are some problems, which islet
The 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 usedlet
When declaring, it uses what some people call lexical scope or chunk scope. And usevar
The declared variables are different,block-scope
The scope of a block level scoped variable will be leaked to the function it contains, and in its nearest containing block orfor-loop
Is 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.
staycatch
The 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’tlet
Statements, 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
aboutvar
Declaration, 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,let
Your 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 variablessumMatrix
Function:
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 withvar
When 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 althoughif
The block has been executed and we can still access it.
Think back, before ussetTimeout
In the example, we finally need to useIIFE
To 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 previoussetTimeout
For example, only uselet
Statement.
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 const
statement
const
Declaration 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 arereadonly
of
1.9 let
Andconst
compare
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 uselet
Statement.
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.a
ando.b
A new variable was created ina
andb
。 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;
- 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: newName1
pronounce 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;
- 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?
expressb
Is optional, so it may be undefined.keepWholeObject
Now there is onewholeObject
Variables and propertiesa
andb
, even ifb
Is 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 rememberdestructured
Property, 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 makesbothPlus
The value of is[0, 1, 2, 3, 4, 5]
。 Expand createfirst
andsecond
Shallow 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
x
The type of variable is inferred asnumber
。 This inference takes place inInitialize variables and members、Set 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 examplex
We must consider the type of each array element. Here we get the choice of two array types:number
andnull
。 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 tozoo
Inferred asAnimal[]
But because there is no strict meaning in the arrayAnimal
Type, 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.onmousedown
The 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 infermouseEvent
The type of the parameter, which does contain aButtonButton attribute, but does not containkangarooKangaroo property.
The reason for this iswindow
Already 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.onscroll
I know the truthuiEvent
It’s aUIEvent
, not like the previous exampleMouseEvent
。 UIEvent
Object 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 implicitlyany
And will not issue errors (unless you usenoImplicitAny
Options).
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 recordundefined
Because uievent does not have a property named button.
Context typing is applicable in many cases. Common situations includeParameters of function call、Right side of assignment、Type 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
, Elephant
andSnake
。 Among them,Animal
Can be selected by the best common type algorithm.
Typescript learning advanced Chapter 3: Enumeration
Enums
Is 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 whichUp
Is initialized to 1, and all the following members are automatically incremented from this point. let me put it another way,Direction.Up
The value of is 1,Down
It’s 2,Left
It’s 3,Right
It’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 first,Either 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 assigned
0
:
// 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:
- The literal meaning of enumeration expression (basically a string literal or a numeric literal);
- A reference to a previously defined constant enumeration member (can be from a different enumeration);
- A constant enumeration expression in parentheses;
- Applied to constant enumeration expressions
+
,-
,~
One of the single operators; +
,-
,*
,/
,%
,<<
,>>
,&
,|
,^
Binary operator with constant enumeration expression as operand.
If the constant enumeration expression is evaluated asNaN
orInfinity
, 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 checkx
Is it notE.Foo
。 If this check is successful, then our||
Will be short circuited,if
The body of the statement will run. However, if the check is not successful, then x can only beE.Foo
So let’s see if it’s equal toE.Bar
It 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
althoughEnum
Is a real object that exists at runtime,keyof
Keywords work differently from what you expect from the object. Instead, usekeyof
typeof
To get one that will allEnum
The 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.
const
enumeration
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 useconst
Enumeration. Constant enumeration is based on our enumerationconst
Modifier.
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/if
Enumerations 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 willType
All 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 aType
The type composed of all the attributes of. It is required to be set. AndPartial
contrary:
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,Type
All properties of are set toreadonly
This 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 fromType
Select 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 fromType
Exclude all that can be assigned toExcludedUnion
To 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 fromType
Can be allocated toUnion
Allunion
Member 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 typeType
ofparameterBuild 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)Type
Is not a function; otherwisenever
Type).
// 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 functionType
Type 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 aType
inInstance 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 typethis
Parameter type, if the function type does notthis
Parameter; otherwiseunknown
。
function toHex(this: Number) {
return this.toString(16);
}
// n: number
function numberToString(n: ThisParameterType) {
return toHex.apply(n);
}
4.15 OmitThisParameter
removeType
ofthis
Parameters. IfType
Not explicitly statedthis
Parameter, the result is justType
。 Otherwise, nonethis
The new function type of the parameter will be fromType
establish. 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 contextthis
Tag of type。 be careful,Must be enablednoImplicitThis
Flag 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,makeObject
Of the parametersmethods
Object has aThisType
The context type of the method objectthis
The 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,symbol
Is a primitive data type, likenumber
andstring
Same.
symbol
Value is by callingSymbol
Constructor.
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,Symbols
Can be used as a key for object properties.
const sym = Symbol();
let obj = {
[sym]: "value",
};
console.log(obj[sym]); // "value"
Symbols
It 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 tosymbols
As the only literal symbol, a special type is providedunique symbol
。 unique symbol
yessymbol
A 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 usetypeof
Operator. 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 symbol
Have a completely independent identity, not twounique symbol
The 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-definedsymbols
In addition, there is a famous built-insymbols
。 Built in symbols are used to represent internal language behavior.
Here is a famoussymbols
List:
5.2.1 Symbol.hasInstance
OneDetermine constructor object, whether to identify an object as one of the instances of the constructor. frominstanceof
Semantic call of operator.
5.2.2 Symbol.isConcatSpreadable
A Boolean value indicating that an object should beArray.prototype.concat
Tile to its array elements.
5.2.3 Symbol.iterator
Method that returns the default iterator of an object. coverfor-of
Called 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.match
Method call.
5.2.5 Symbol.replace
A regular expression method used to replace matching substrings in a string. fromString.prototype.replace
Method call.
5.2.6 Symbol.search
A regular expression method that returns the index in the string that matches the regular expression. fromString.prototype.search
Method 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.split
Method call.
5.2.9 Symbol.toPrimitive
A method of converting an object to a corresponding primitive value. fromToPrimitive
Abstract 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.toString
Call.
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#
orJava
In such a nominally typed language, the corresponding code will be an error,becauseDog
Class does not explicitly describe itself asPet
Implementer 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 ify
At least withx
The same member, thenx
Andy
Is 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 checkdog
Can be assigned topet
, compiler checkpet
For each attribute to finddog
The corresponding compatibility attribute in. under these circumstances,dog
There must be one namedname
Is 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,dog
There is an extraowner
Property, 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 checkx
Can I assign toy
First, let’s look at the parameter list.x
Each parameter iny
There 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,x
Each parameter iny
There is a corresponding compatible parameter in, so this assignment is allowed.
The second assignment is an error becausey
There is onex
There is no necessary second parameter, so this assignment is not allowed.
You may wonder why we allow things like the one in the exampley = x
Then “discard” the parameter. This assignment is allowed because it is common in JavaScript to ignore additional function parameters. For example,Array#forEach
Three 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 flagstrictFunctionTypes
An 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 positionundefined
The 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,x
andy
Are compatible because their structures do not use type parameters in a differentiated manner. By givingEmpty
Adding 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 inimplements
andextends
Clause.
6.12 any
,unknown
,object
,void
,undefined
,null
, andnever
Distributability
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 closedstrictNullChecks
Is a compatible combination
any | unknown | object | void | undefined | null | never | |
---|---|---|---|---|---|---|---|
any | ✓ | ✓ | ✓ | ✓ | ✓ | ✕ | |
unknown | ✓ | ✕ | ✕ | ✕ | ✕ | ✕ | |
object | ✓ | ✓ | ✕ | ✕ | ✕ | ✕ | |
void | ✓ | ✓ | ✕ | ✕ | ✕ | ✕ | |
undefined | ✓ | ✓ | ✓ | ✓ | ✓ | ✕ | |
null | ✓ | ✓ | ✓ | ✓ | ✓ | ✕ | |
never | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
- Everything can be assigned to yourself.
any
andunknown
In terms of distributable content, it is the same, but the difference isunknown
Can’t be assigned to anything exceptany
。unknown
andnever
It’s like the opposite of each other. Everything can be assigned tounknown
,never
Can be allocated to everything. Nothing can be assigned tonever
,unknown
Can’t be assigned to anything (exceptany
)。void
Cannot be assigned to anything, with the following exceptions:any
、unknown
、never
、undefined
andnull
(if)strictNullChecks
It is closed (see table for details).- When
strictNullChecks
When closed,null
andundefined
Andnever
Similar: it can be assigned to most types, and most types cannot be assigned to them. They can assign values to each other. - When
strictNullChecks
When on,null
andundefined
Your behavior is more likevoid
: exceptany
、unknown
、never
andvoid
Can’t assign to anything except(undefined
Can always be assigned tovoid
)。
Typescript learning advanced Chapter 7: iterator and generation
7.1 traversal
If an object hasSymbol.iterator
Property, it is considered to be iterative. Some built-in types, such asArray
、 Map
、 Set
、 String
、 Int32Array
、 Uint32Array
Wait, they ‘ve already been implementedSymbol.iterator
Properties. On objectSymbol.iterato
The R function returns a list of values to iterate over.
7.1.1 Iterable
Interface
Iterable
Is 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 ... of
statement
for... of
Loop over an iteratable object and call theSymbol.iterator
Properties. Here is a simple example of an arrayfor... of
Cycle.
let someArray = [1, "string", false];
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}
7.1.3 for ... of
Andfor ... in
statement
for...of
andfor...in
Statements are iterated on the list; But the value of iteration is different,for...in
Returns the of the iterated objectKey value list, andfor...of
Returns 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...in
Operate on any object; It acts as a way to check the properties on the object. On the other hand,for...of
Mainly interested in the values of iteratable objects. imageMap
andSet
Such built-in objects are implementedSymbol.iterator
Property 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 inArray
Used on values of type.
Use on non array valuesfor...of
A loop is an error, even if these non array values are implementedSymbol.iterator
Properties.
For example, the compiler will befor...
The loop generates a simplefor
Cycle.
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...of
Loop 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.json
Enabled inexperimentalDecorators
Compiler 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 classesstatement、method、Accessor、Property or parameterCome on. Use of decorator@expression
In whichexpression
Must be evaluated as a function,This function will be called at runtime with information about the decorated declaration。
For example, for decorators@sealed
, we cansealed
The 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:
- The expression of each decorator is evaluated from top to bottom.
- 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:
- For each instance member, first the parameter decorator, then the method, accessor, or property decorator.
- For each static member, first the parameter decorator, then the method, accessor, or property decorator.
- Parameter decorators are applied to constructors.
- 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 asdeclare
Class).
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 toBugReport
Class 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@sealed
Decorator
// 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@sealed
When executed, it will close both the constructor and its prototype, so it will prevent access at run timeBugReport.prototype
Or by definitionBugReport
To 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 pairsBugReport
Subclass.
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;