typescript枚举,类型推论,类型兼容性,高级类型,Symbols(学习笔记非干货)

喜你入骨 提交于 2020-05-02 18:28:24

枚举部分 Enumeration part 使用枚举我们可以定义一些有名字的数字常量。 枚举通过 enum关键字来定义。 Using enumerations, we can define some numeric constants with names. Enumeration is defined by the enum keyword.

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

枚举是在运行时真正存在的一个对象,其中一个原因是因为这样可以从枚举值到枚举名进行反向映射 Enumeration is an object that really exists at runtime, one of the reasons is that it maps backwards from enumeration values to enumeration names.

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

引用枚举成员总会生成一次属性访问并且永远不会内联。 在大多数情况下这是很好的并且正确的解决方案。 然而有时候需求却比较严格。 Reference enumeration members always generate an attribute access and never inline. In most cases this is a good and correct solution. Sometimes, however, demand is more stringent. 当访问枚举值时,为了避免生成多余的代码和间接引用,可以使用常数枚举。 When accessing enumerated values, constant enumerations can be used to avoid generating redundant code and indirect references. 常数枚举是在enum关键字前使用const修饰符。 Constant enumeration uses const modifiers before enum keywords.

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

常数枚举只能使用常数枚举表达式并且不同于常规的枚举的是它们在编译阶段会被删除。 常数枚举成员在使用的地方被内联进来。 这是因为常数枚举不可能有计算成员。 Constant enumerations can only use constant enumeration expressions and, unlike conventional enumerations, they are deleted during compilation. Constant enumeration members are inlined where they are used. This is because constant enumerations cannot have computational members.

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

外部枚举 External enumeration 外部枚举用来描述已经存在的枚举类型的形状 External enumerations are used to describe the shape of existing enumeration types

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

外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。 对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。 There is an important difference between external and non-external enumerations. In normal enumerations, members without initialization methods are treated as constant members. For non-constant external enumerations, the absence of initialization methods is considered computational.

类型推论 type inference 最佳通用类型 Best Universal Type 当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。 When it is necessary to infer types from several expressions, the types of these expressions are used to infer the most appropriate generic type.

let x=[0,1,null];

计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。 Computing generic type algorithms takes into account all candidate types and gives a type compatible with all candidate types. 由于最终的通用类型取自候选类型,有些时候候选类型共享相同的通用类型, 但是却没有一个类型能做为所有候选类型的类型。例如: Because the final generic type is taken from the candidate type, sometimes the candidate type shares the same generic type, but none of them can be used as the type of all candidate types. For example:

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

我们想让zoo被推断为Animal[]类型,但是这个数组里没有对象是Animal类型的,因此不能推断出这个结果。 为了更正,当候选类型不能使用的时候我们需要明确的指出类型: We want zoo to be inferred as an Animal [] type, but no object in this array is an Animal type, so we can't infer this result. To correct this, we need to specify the type when the candidate type is not available:

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

如果没有找到最佳通用类型的话,类型推论的结果是空对象类型,{}。 因为这个类型没有任何成员,所以访问其成员的时候会报错。 If the best generic type is not found, the result of type inference is an empty object type, {}. Because there are no members of this type, there is an error when accessing its members. 上下文类型 Context type TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。 按上下文归类会发生在表达式的类型与所处的位置相关时。比如: TypeScript type inferences may also proceed in the opposite direction. This is called "categorizing by context". Categorization by context occurs when the type of expression is related to its location. For example:

window.onmousedown=function(mouseEvent){
    console.log(mouseEvent.button);
}

TypeScript类型检查器使用Window.onmousedown函数的类型来推断右边函数表达式的类型。 因此,就能推断出 mouseEvent参数的类型了。 如果函数表达式不是在上下文类型的位置, mouseEvent参数的类型需要指定为any,这样也不会报错了。 The TypeScript type checker uses the type of the Window. onmousedown function to infer the type of the right-hand function expression. Therefore, the type of mouseEvent parameter can be inferred. If the function expression is not located in the context type, the type of the mouseEvent parameter needs to be specified as any, which will not cause an error.

window.onmousedown=function(mouseEvent:any){
    console.log(mouseEvent.button);
}

上下文类型也会做为最佳通用类型的候选类型。 Context types are also candidates for the best generic types.

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

类型兼容性 type compatibility TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型形成对比。 Type compatibility in TypeScript is based on structural subtypes. Structural types are a way of describing types using only their members. It is in contrast to the nominal type.

interface Named{
    name:string;
}
class Person{
    name:string;
}
let p:Named;
p=new Person();

因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量, 所以使用结构类型系统来描述这些类型比使用名义类型系统更好。 Because anonymous objects such as function expressions and object literals are widely used in JavaScript, it is better to describe these types using a structured type system than using a nominal type system. 关于可靠性的注意事项 Notes on Reliability


interface Named{
    name:string;
}
let x:Named;
let y={name:'Alice',location:'Seattle'};
x=y;
``` y必须包含名字是name的string类型成员。y满足条件,因此赋值正确。
Y must contain a string type member whose name is name. Y satisfies the condition, so the assignment is correct.
```js
function greet(n:Named){
    alert('Hello'+n.name);
}
greet(y);

注意,y有个额外的location属性,但这不会引发错误。 只有目标类型(这里是 Named)的成员会被一一检查是否兼容。 Note that y has an additional location attribute, but this does not cause an error. Only members of the target type (Named in this case) are checked for compatibility. 这个比较过程是递归进行的,检查每个成员及子成员。 This comparison process is carried out recursively, checking each member and its submembers. 比较两个函数 Compare two functions 相对来讲,在比较原始类型和对象类型的时候是比较容易理解的,问题是如何判断两个函数是兼容的。 下面我们从两个简单的函数入手,它们仅是参数列表略有不同: Relatively speaking, it is easier to understand when comparing the original type and object type. The problem is how to judge the compatibility of the two functions. Let's start with two simple functions, which are just slightly different from the list of parameters.

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

Array#forEach给回调函数传3个参数:数组元素,索引和整个数组。 尽管如此,传入一个只使用第一个参数的回调函数也是很有用的: Array#forEach pass three parameters to the callback function: array element, index and whole array. Nevertheless, it is useful to pass in a callback function that uses only the first parameter:et items=[1,2,3];

items.forEach((item)=>console.log(item));

下面来看看如何处理返回值类型,创建两个仅是返回值类型不同的函数: Let's look at how to deal with return value types and create two functions with only different return value types:

let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error because x() lacks a location property

类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。 The type system enforces that the return value type of the source function must be a subtype of the return value type of the objective function. 当比较函数参数类型时,只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的, 因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 When comparing the types of function parameters, only when the source function parameters can be assigned to the objective function or vice versa can the assignment be successful. This is unstable because the caller may pass in a function with more precise type information, but the incoming function is called with less precise type information. 实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。 In fact, this is rarely an error, and it implements many common patterns in JavaScript.

enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }

function listenEvent(eventType: EventType, handler: (n: Event) => void) {
    /* ... */
}
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
listenEvent(EventType.Mouse, (e: number) => console.log(e));

可选参数及剩余参数 Optional and residual parameters 当一个函数有剩余参数时,它被当做无限个可选参数。 When a function has residual parameters, it is treated as an infinite number of optional parameters. 这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的, 因为对于大多数函数来说相当于传递了一些undefinded。 This is unstable for type systems, but from a runtime point of view, optional parameters are generally not mandatory, because for most functions they are equivalent to passing some undefined. 常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用: Common functions receive a callback function and call it with parameters that are predictable for programmers but uncertain for type systems:

function invokeLater(args:any[],callback:(...args:any[])=>void){}
invokeLater([1,2],(x,y)=>console.log(x+','+y));
// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));

枚举 enumeration 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如, Enumeration types are compatible with numeric types, and numeric types are compatible with enumeration types. Different enumeration types are incompatible. For example,

enum Status {Ready,Waiting};
enum Color {Red,Blue,Green};
let status=Status.Ready;
status=Color.Green;

类 class 类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。 Classes are similar to object literals and interfaces, but differ in one respect: classes have types of static and instance parts. When comparing objects of two class types, only the members of the instance are compared. Static members and constructors are not within the scope of comparison.

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

类的私有成员 Private members of classes 私有成员会影响兼容性判断。 当类的实例用来检查兼容时,如果它包含一个私有成员,那么目标类型必须包含来自同一个类的这个私有成员。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。 Private members influence compatibility judgments. When an instance of a class is used to check compatibility, if it contains a private member, the target type must contain the private member from the same class. This allows subclasses to be assigned to parent classes, but cannot be assigned to other classes of the same type.

泛型 generic paradigm 因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。比如, Because TypeScript is a structured type system, type parameters only affect the type of result that is used as part of the type. For example,

interface Empty<T>{}
let x:Empty<number>;
let y:Empty<string>;
x=y;//

对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。 然后用结果类型进行比较,就像上面第一个例子。 When generic parameters of a generic type are not specified, all generic parameters are compared as any. Then compare with the result type, as in the first example above.

let identity=function<T>(x:T):T{}
let reverse=function<U>(y:U):U{}
identity=reverse;

在TypeScript里,有两种类型的兼容性:子类型与赋值。 它们的不同点在于,赋值扩展了子类型兼容, 允许给 any赋值或从any取值和允许数字赋值给枚举类型或枚举类型赋值给数字。 In TypeScript, there are two types of compatibility: subtypes and assignments. The difference between t hem is that assignment extends subtype compatibility, allows assignment to any or from any and allows number assignment to enumeration type or enumeration type assignment to numbers. 语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来 控制的甚至在implements和extends语句里。 Different parts of the language use different mechanisms. In fact, type compatibility is controlled by assignment compatibility even in implements and extends statements. 高级类型 Advanced type 交叉类型(Intersection Types) 我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript里发生这种情况的场合很多!) 下面是如何创建混入的一个简单例子: Most of us see the use of cross-types in mixins or other places that are not suitable for typical object-oriented models. (There are many situations where this happens in JavaScript!) Here's a simple example of how to create a mix-in:

function extend<T,U>(first:T,second:U):T&U{
    let result=<T&U>{};
    for(let id in first){
        (<any>result)[id]=(<any>first)[id];
    }
    for(let id in second){
        if(!result.hasOwnProperty(id)){
            (<any>result)[id]=(<any>second)[id];
        }
    }
    return result;
}
class Person{
    constructor(public name:string){}
}
interface Loggable{
    log():void;
}
class ConsoleLogger implements Loggable{
    log(){}
}
var jim=extend(new Person("Jim"),new ConsoleLogger());
var n=jim.name;
jim.log();

联合类型 Joint type 联合类型与交叉类型很有关联,但是使用上却完全不同。 偶尔你会遇到这种情况, 一个代码库希望传入 number或string类型的参数。 例如下面的函数 Joint types are closely related to cross types, but they are quite different in use. Occasionally you will encounter this situation where a code base wants to pass in parameters of type number or string. For example, the following functions

function padLeft(value:string,padding:any){
    if(typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}
padLeft("Hello world",4);//"Hello world"
let indentedString = padLeft("Hello world", true); // 编译阶段通过,运行时报错
function padLeft(value: string, padding: string | number) {
    // ...
}
let indentedString = padLeft("Hello world", true); 

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。 If a value is a federated type, we can only access members common to all types of the federated type.


interface Bird{
    fly();
    layEggs();
}
interface Fish{
    swim();
    layEggs();
}
function getSmallPet():Fish|Bird{

}
let pet=getSmallPet();
pet.layEggs();//ok
pet.swim();//error

类型保护与区分类型 Type Protection and Type Differentiation 联合类型非常适合这样的情形,可接收的值有不同的类型。 当我们想明确地知道是否拿到 Fish时会怎么做? JavaScript里常用来区分2个可能值的方法是检查它们是否存在。 像之前提到的,我们只能访问联合类型的所有类型中共有的成员。 Joint types are well suited for this situation, and the values that can be received are of different types. What do we do when we want to know exactly if we get Fish? The common way to distinguish two possible values in JavaScript is to check whether they exist. As mentioned earlier, we can only access members common to all types of Federation types.

let pet = getSmallPet();

每一个成员访问都会报错 Every member will report an error when visiting

if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}

为了让这段代码工作,我们要使用类型断言: To make this code work, we use type assertions:

let pet=getSmallPet();
if((<Fish>pet).swim){
    (<Fish>pet).swim();
}else{
    (<Bird>pet).fly();
}

用户自定义的类型保护 User-defined type protection 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型断言: Type protection is expressions that are checked at runtime to ensure types in a scope. To define a type protection, we simply define a function whose return value is a type assertion:

function isFish(pet:Fish|Bird):pet is Fish{
    return(<Fish>pet).swim!==undefined;
}

在这个例子里,pet is Fish就是类型断言。 一个断言是 parameterName is Type这种形式, parameterName必须是来自于当前函数签名里的一个参数名。 In this case, pet is Fish is a type assertion. An assertion is in the form of parameterName is Type, which must be a parameter name from the current function signature. 每当使用一些变量调用isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。 Whenever isFish is called with some variables, TypeScript reduces the variable to that specific type, as long as it is compatible with the original type of the variable. 'swim' 和 'fly' 调用都没有问题了 'swim'and'fly' calls are fine

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

typeof类型保护 Typeof type protection

function isNumber(x:any):x is number{
    return typeof x==="number";
}
function isString(x:any):x is string{
    return typeof x==="string";
}
function padLeft(value:string,padding:string|number){
    if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
    }
    if (isString(padding)) {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

现在我们不必将 typeof x === "number"抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。 Now we don't have to abstract typeof x == "number" into a function, because TypeScript can recognize it as a type protection. That is to say, we can check the type directly in the code.

function padLeft(value:string,padding:string|number){
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

instanceof类型保护 Instanceof type protection instanceof类型保护是通过构造函数来细化类型的一种方式. Instance of type protection is a way to refine types by constructors.

interface Padder {
    getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}

class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadder("  ");
}


类型为SpaceRepeatingPadder | StringPadder Type is SpaceRepeatingPadder | StringPadder

let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder'
}

instanceof的右侧要求是一个构造函数,TypeScript将细化为: The right side of instanceof requires a constructor, and TypeScript refines it to:

此构造函数的prototype属性的类型,如果它的类型不为any的话 The type of the prototype attribute of this constructor if its type is not any 构造签名所返回的类型的联合 Construct a union of the types returned by the signature 类型别名 Type alias 类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型, 元组以及其它任何你需要手写的类型。 Type aliases give a new name to a type. Type aliases are sometimes similar to interfaces, but they can work on primitive values, federated types, tuples, and any other type you need to write by hand.

type Name=string;
type NameResolver=()=>string;
type NameOrResolver=Name|NameResolver;
function getName(n:NameOrResolver):Name{
    if (typeof n === 'string') {
        return n;
    }
    else {
        return n();
    }
}

起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。 给原始类型起别名通常没什么用, 尽管可以做为文档的一种形式使用。 Type - It creates a new name to refer to that type. Aliases for primitive types are usually useless, although they can be used as a form of documentation. 同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入: Like interfaces, type aliases can also be generic - we can add type parameters and pass in on the right side of the alias declaration:

type Container<T>={value:T};
type Tree<T>={
    value:T;
    left:Tree<T>;
    right:Tree<T>;
}
type LinkedList<T>=T&{next:LinkedList<T>};
interface Person{
    name:string;
}
var people:LinkedList<Person>;
var s=people.name;
var s=people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;

接口 vs. 类型别名 Interface vs. Type alias 其一,接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。 First, the interface creates a new name that can be used anywhere else. Type aliases do not create new names -- for example, no aliases are used for error messages.

type Alias={num:number}
interface Interface{
    num:number;
}
declare function aliased(arg:Alias):Alias;
declare function interface(arg:Interface):Interface;

另一个重要区别是类型别名不能被extends和implements(自己也不能extends和implements其它类型)。 因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。 Another important difference is that type aliases cannot be extended and implements (they cannot extend and implements other types themselves). Because objects in software should be open to extensions, but closed to modifications, you should try to use interfaces instead of type aliases. 另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。 On the other hand, if you can't describe a type through an interface and you need to use a federated type or tuple type, you usually use a type alias. 字符串字面量类型 String literal type 字符串字面量类型允许你指定字符串必须的固定值。 The string literal type allows you to specify the fixed values that a string must have. 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 In practical applications, string literal type can be well matched with joint type, type protection and type aliases.

type Easing="ease-in"|"ease-out"|"ease-in-out";
class UIElment{
    animate(dx:number,dy:number,easing:Easing){
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {
        }
        else if (easing === "ease-in-out") {
        }
        else {
            // error! should not pass null or undefined.
        }
    }
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

字符串字面量类型还可以用于区分函数重载: String literal types can also be used to distinguish function overloads:

function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
    // ... code goes here ...
}

可辨识联合(Discriminated Unions) 你可以合并字符串字面量类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式, 它也称做标签联合或代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合; 而TypeScript则基于已有的JavaScript模式。 它具有4个要素: You can combine string literal types, associative types, type protection and type aliases to create an advanced pattern called recognizable associations, which are also called label associations or algebraic data types. Recognizable associations are useful in functional programming. Some languages automatically identify federations for you; TypeScript is based on existing JavaScript patterns. It has four elements:

具有普通的字符串字面量属性—可辨识的特征 It has the common literal property of strings - identifiable features. 一个类型别名包含了那些类型的联合—联合 A type alias contains those types of unions - unions. 此属性上的类型保护。 Type protection on this property.

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

首先我们声明了将要联合的接口。 每个接口都有 kind属性但有不同的字符器字面量类型。 kind属性称做可辨识的特征或标签。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起: First we declared the interface to be joined. Each interface has kind properties but has different character literal types. Kind attributes are called identifiable features or tags. Other attributes are specific to each interface. Note that there is no connection between interfaces at present. Now let's put them together: 现在我们使用可辨识联合: Now we use identifiable unions:

function area(s:Shape){
    switch(s.kind){
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
}

完整性检查 Integrity check 当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了 Triangle到Shape, 我们同时还需要更新area: When all identifiable joint changes are not covered, we want the compiler to notify us. For example, if we add Triangle to Shape, we also need to update the area:

type Shape = Square | Rectangle | Circle | Triangle;
function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
    // should error here - we didn't handle case "triangle"
}

两种方式可以实现。 首先是启用 --strictNullChecks并且指定一个返回值类型: Two ways can be achieved. The first is to enable -- strictNullChecks and specify a return value type:

function area(s: Shape): number { // error: returns number | undefined
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
}

第二种方法使用never类型,编译器用它来进行完整性检查: The second method uses the never type, which is used by the compiler for integrity checking:

function assertNever(x:never):never{
    throw new Error("Unexpected object"+x);
}
function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
        default: return assertNever(s); // error here if there are missing cases
    }
}

多态的this类型 This Type of Polymorphism 多态的this类型表示的是某个包含类或接口的子类型。 这被称做 F-bounded多态性。 它能很容易的表现连贯接口间的继承, A polymorphic this type represents a subtype containing a class or interface. This is known as the F-bounded polymorphism. It can easily represent inheritance between coherent interfaces.

class BasicCalculator{
    public constructor(protected value:number=0){}
    public currentValue():number{
        return this.value;
    }
    public add(operand:number):this{
        this.value+=operand;
        return this;
    }
    public multiply(operand:number):this{
        this.value*=operand;
        return this;
    }
}
let v=new BasicCalculator(2)
            .multiply(5)
            .add(1)
            .currentValue()

由于这个类使用了this类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。 Because this class uses this type, you can inherit it, and the new class can use the previous method directly without any changes.

class ScientificCalculator extends BasicCalculator{
    public constructor(value=0){
        super(value);
    }
    public sin(){
        this.value=Math.sin(this.value);
        return this;
    }
}
let v=new ScientificCalculator(2)
            .multiply(5)
            .sin()
            .add(1)
            .currentValue();

Symbols symbol成为了一种新的原生类型 Symbol has become a new primitive type

let sym1=Symbol();
let sym2=Symbol("key");

Symbols是不可改变且唯一的。 Symbols are immutable and unique.

let sym2=Symbol("key");
let sym3=Symbol("key");
sym2===sym3;//false

像字符串一样,symbols也可以被用做对象属性的键。 Like strings, symbols can also be used as keys to object attributes.

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

Symbols也可以与计算出的属性名声明相结合来声明对象的属性和类成员。 Symbols can also be combined with computed attribute name declarations to declare the attributes and class members of an object.

const getClassNameSymbol=Symbol();
class C{
    [getClassNameSymbol](){
        return "C";
    }
}
let c=new C();
let className=c[getClassNameSymbol]();//"C"
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!