[TypeScript] 编程实践之1: Google的TypeScript代码风格4:表达式

4 表达式

本章描述TypeScript为JavaScript表达式提供类型推断和类型检查的方式。 TypeScript的类型分析完全在编译时进行,并且不增加表达式评估的运行时开销。

TypeScript的键入规则为每个表达式构造定义一个类型。 例如,文字123的类型是Number原语类型,而对象文字{{a:10,b:“ hello”}的类型是{a:number; b:字符串; }。 本章的各节详细介绍了这些规则。

除了类型推断和类型检查之外,TypeScript还使用以下结构来扩展JavaScript表达式:

  • 函数表达式和箭头函数中的可选参数和返回类型注释。
  • 在函数调用中输入参数。
  • 输入断言。

除非后面各节中另有说明,否则TypeScript表达式和从它们生成的JavaScript表达式是相同的。

4.1 值和引用

表达式被分类为值或引用。 引用是允许作为赋值目标的表达式子集。 具体来说,引用是标识符(第4.3节),括号(第4.8节)和属性访问(第4.13节)的组合。 本章中描述的所有其他表达式构造都归类为值。

4.2 this关键字

表达式中此类型取决于引用发生的位置:

  • 在构造函数,实例成员函数,实例成员访问器或实例成员变量初始化器中,这是包含类的this类型(第3.6.3节)。
  • 在静态成员函数或静态成员访问器中,此类型是包含类的构造函数类型。
  • 在函数声明或函数表达式中,其类型为Any。
  • 在全局命名空间中,此类型为Any。

在所有其他上下文中,引用此是编译时错误。

注意,箭头函数(第4.11节)没有此参数,而是保留了其封闭上下文的this。

4.3 标识符

当表达式是IdentifierReference时,该表达式引用具有该名称的最嵌套的命名空间,类,枚举,函数,变量或参数,其名称范围(第2.4节)包括引用的位置。 此类表达式的类型是与引用实体关联的类型:

  • 对于命名空间,是与命名空间实例关联的对象类型。
  • 对于类,与构造函数对象关联的构造类型。
  • 对于枚举,与枚举对象关联的对象类型。
  • 对于功能,是与功能对象关联的功能类型。
  • 对于变量,变量的类型。
  • 对于参数,参数的类型。

引用变量或参数的标识符表达式被分类为引用。 引用任何其他种类的实体的标识符表达式被分类为值(因此不能成为分配的目标)。

4.4 文法

文字的类型如下:

  • 空文字的类型是Null基本类型。
  • 文字true和false的类型是布尔基元类型。
  • 数字文字的类型是Number原语类型。
  • 字符串文字的类型是String基本类型。
  • 正则表达式文字的类型是全局接口类型’RegExp’。

4.5 Object文法

对象文字被扩展为支持方法中的类型注释以及获取和设置访问器。

PropertyDefinition :(Modified)
	IdentifierReference
	CoverInitializedName
	PropertyName:AssignmentExpression
	PropertyName CallSignature {FunctionBody}
	GetAccessor
	SetAccessor

GetAccessor:
	get PropertyName()TypeAnnotationopt {FunctionBody}

SetAccessor:
	set Property(BindingIdentifierOrPattern TypeAnnotationopt){FunctionBody}

对象文字的类型是一种对象类型,其属性集由对象文字中的属性分配指定。 get和set访问器可以指定相同的属性名称,但否则为同一属性指定多个属性分配是错误的。

表格的简写属性分配

prop

相当于

prop : prop

同样

f ( ... ) { ... }

相当于

f : function ( ... ) { ... }

对象文字中的每个属性分配按以下方式处理:

  • 如果对象文字是上下文类型的,并且上下文类型包含具有匹配名称的属性,则属性分配将根据该属性的类型进行上下文类型化。
  • 否则,如果在上下文上键入对象文字,如果上下文类型包含数字索引签名,并且如果属性分配指定了数字属性名称,则属性分配由数字索引签名的类型在上下文中键入。
  • 否则,如果对象文字是上下文类型的,并且上下文类型包含字符串索引签名,则属性分配将通过字符串索引签名的类型上下文类型。
  • 否则,将在没有上下文类型的情况下处理属性分配。

由形式为Name的属性分配引入的属性类型:Expr是Expr的类型。

get访问器声明的处理方式与不带参数的普通函数声明(第6.1节)相同。设置访问者声明的处理方式与具有单个参数和Void返回类型的普通函数声明的处理方式相同。同时为属性声明get和set访问器时:

  • 如果两个访问器都包含类型注释,则指定的类型必须相同。
  • 如果只有一个访问器包含类型注释,则另一个访问器的行为就好像它具有相同的类型注释。
  • 如果两个访问器都不包含类型注释,则get访问器的推断返回类型将成为set访问器的参数类型。

如果为属性声明了get访问器,则get访问器的返回类型将成为属性的类型。如果仅为属性声明集合访问器,则集合访问器的参数类型(如果不存在类型注释,则可以为Any)成为属性的类型。

当对象文字通过包含字符串索引签名的类型在上下文中键入时,对象文字的结果类型包括字符串索引签名,该字符串索引签名具有在对象文字中声明的属性的类型的并集类型,或者为Undefined类型(如果存在)对象文字为空。同样,当对象文字通过包含数字索引签名的类型在上下文中键入时,对象文字的结果类型包括数字索引签名,该数字索引签名具有声明的数字命名属性的类型的联合类型(第3.9.4节)在对象文字中,如果对象文字未声明任何数字命名的属性,则为Undefined类型。

如果属性分配的PropertyName是不代表众所周知的符号(2.2.3)的计算的属性名称,则该构造被视为动态属性分配。以下规则适用于动态属性分配:

  • 动态属性分配不会在对象文字的类型中引入属性。
  • 动态属性分配的属性名称表达式必须为Any或String,Number或Symbol原语类型。
  • 如果属性名称表达式的类型为Any或Number原语类型,则与动态属性分配关联的名称被视为数字属性名称。

4.6 数组文法

数组文法

[ expr1, expr2, ..., exprN ]

表示取决于上下文的数组类型(第3.3.2节)或元组类型(第3.3.3节)的值。

非空数组文字中的每个元素表达式的处理如下:

  • 如果数组文字不包含任何扩展元素,并且数组文字是通过类型T在上下文中键入的(第4.23节),并且T具有数字名称N的属性,其中N是数组文字中元素表达式的索引,元素表达式是根据该属性的类型在上下文中键入的。
  • 否则,如果数组文字是由带有数字索引签名的类型T在上下文中键入的,则元素表达式是由数字索引签名的类型在上下文中键入的。
  • 否则,元素表达式不会在上下文中键入。

数组文字表达式的结果类型确定如下:

  • 如果数组文字为空,则结果类型为元素类型为Undefined的数组类型。
  • 否则,如果数组文字不包含任何散布元素,并且使用类似元组的类型在上下文中进行类型化(第3.3.3节),则结果类型为根据元素表达式的类型构造的元组类型。
  • 否则,如果数组文字不包含任何散布元素,并且是解构分配中的数组分配模式(第4.21.1节),则结果类型为根据元素表达式的类型构造的元组类型。
  • 否则,结果类型是一个数组类型,其元素类型是未扩展元素表达式的类型与扩展元素表达式的数字索引签名类型的并集。

扩展元素必须指定类似数组类型的表达式(第3.3.2节),否则会发生错误。

TODO:编译器当前不支持将扩展运算符应用于字符串(将字符串的各个字符扩展为字符串数组)。最终将允许这样做,但前提是代码生成目标是ECMAScript 2015或更高版本。

TODO:将迭代器扩展到数组文字中的文档。

上面的规则意味着数组文字始终是数组类型的,除非它是由类似元组的类型在上下文中键入的。例如

var a = [1, 2];                          // number[]  
var b = ["hello", true];                 // (string | boolean)[]  
var c: [number, string] = [3, "three"];  // [number, string]

当输出目标是ECMAScript 3或5时,包含扩展元素的数组文字将重写为concat方法的调用。 例如,作业

var a = [2, 3, 4];  
var b = [0, 1, ...a, 5, 6];

可以重写为

var a = [2, 3, 4];  
var b = [0, 1].concat(a, [5, 6]);

4.7 模板文法

TODO:模板文法

4.8 括弧

带括弧的表达式

( expr )

具有与包含的表达式本身相同的类型和分类。 具体而言,如果将包含的表达式分类为参考,则带括号的表达式也是如此。

4.9 super关键字

可以在表达式中使用super关键字来引用基类属性和基类构造函数。

4.9.1 父类super调用

超级调用由关键字super组成,后跟括号中的参数列表。 超级调用仅在派生类的构造函数中允许,如第8.3.2节所述。

超级调用在此引用的实例上调用基类的构造函数。 使用基类构造函数类型的构造签名作为用于过载解析的候选签名的初始集合,将超级调用作为函数调用(第4.15节)进行处理。 类型参数不能在超级调用中明确指定。 如果基类是泛型类,则用于处理超级调用的类型参数始终是在引用基类的extends子句中指定的类型参数。

超级调用表达式的类型为Void。

在8.7.2节中指定了为超级调用生成的JavaScript代码。

4.9.2 父类属性存取

超级属性访问由关键字super,后跟点和标识符组成。超级属性访问用于从派生类访问基类成员函数,并且在此内容(第4.2节)引用派生类实例或派生类构造函数的上下文中允许访问。特别:

  • 在构造函数,实例成员函数,实例成员访问器或实例成员变量初始化器(其中引用派生类实例)中,允许超级属性访问,并且必须指定基类的公共实例成员函数。
  • 在引用了派生类的构造函数对象的静态成员函数或静态成员访问器中,允许进行超级属性访问,并且必须指定基类的公共静态成员函数。
    在其他上下文中不允许超级属性访问,并且在超级属性访问中不能访问其他类型的基类成员。请注意,在上述结构中嵌套的函数表达式内部不允许进行超级属性访问,因为在此类函数表达式中,该类型为Any。

超级属性访问通常用于访问派生类成员函数中重写的基类成员函数。有关此示例,请参见8.4.2节。

在8.7.2节中指定了为超级属性访问而生成的JavaScript代码。

待办事项:更新部分以在超级属性访问中包括括号符号。

4.10 函数表达式

函数表达式从JavaScript扩展到可选地包括参数和返回类型注释。

FunctionExpression :(Modified)
	function BindingIdentifieropt CallSignature {FunctionBody}

第6章中提供的函数声明的描述也适用于函数表达式,但函数表达式不支持重载。

函数表达式的类型是一种对象类型,其中包含单个调用签名,并带有从函数表达式的签名和主体推断出的参数和返回类型。

当没有类型参数且没有参数类型注释的函数表达式由类型T进行上下文类型化(第4.23节)并且可以从T提取上下文签名S时,该函数表达式将被视为具有明确指定的参数类型注释,如下所示:它们存在于S中。参数通过位置匹配,并且不需要具有匹配的名称。如果函数表达式的参数少于S,则忽略S中的其他参数。如果函数表达式的参数多于S,则所有附加参数均被视为具有Any类型。

同样,当通过函数类型T在上下文中键入没有返回类型注释的函数表达式(第4.23节),并且可以从T中提取上下文签名S时,包含在返回语句中的表达式(第5.10节)将通过返回类型在上下文中进行键入。的S。

如下从功能类型T中提取上下文签名S:

  • 如果T是仅具有一个调用签名的函数类型,并且该调用签名不是通用的,则S是该签名。
  • 如果T是联合类型,则让U为T中具有调用签名的元素类型的集合。如果U中的每种类型恰好具有一个呼叫签名,并且该呼叫签名是非泛型的,并且如果所有签名都是相同的(忽略返回类型),则S是具有相同参数和返回类型的并集的签名。
  • 否则,无法从T中提取上下文签名。

在这个例子中

var f: (s: string) => string = function (s) {  
    return s.toLowerCase();  
};

函数表达式是根据类型’f’在上下文中键入的,并且由于函数表达式没有类型参数或类型注释,因此将从上下文类型中提取其参数类型信息,从而将’s’的类型推断为String原语 类型。

4.11 箭头函数

箭头功能从JavaScript扩展到可选的参数和返回类型注释。

ArrowFormalParameters :(Modified)
	CallSignature

第6章中提供的函数声明的描述也适用于箭头函数,但箭头函数不支持重载。

箭头函数的类型与函数表达式相同(第4.10节)。 同样,以与函数表达式相同的方式在上下文中键入arrow函数的参数和arrow函数主体中的return语句。

当通过函数类型T在上下文中键入带有表达式主体且没有返回类型注释的箭头函数(第4.23节),并且可以从T中提取上下文签名S时,通过返回类型S在上下文中键入表达式主体。

形式的箭头函数表达式

( ... ) => expr

完全等同于

( ... ) => { return expr ; }

此外,表单的箭头函数表达式

id => { ... }  
id => expr

因此,以下示例都是等效的:

(x) => { return Math.sin(x); }  
(x) => Math.sin(x)  
x => { return Math.sin(x); }  
x => Math.sin(x)

函数表达式引入了一个新的动态绑定对象,而箭头函数表达式保留了其封闭上下文的对象。 箭头函数表达式对于编写回调特别有用,否则回调通常具有未定义或意外的含义。

在这个例子中

class Messenger {  
    message = "Hello World";  
    start() {  
        setTimeout(() => alert(this.message), 3000);  
    }  
};

var messenger = new Messenger();  
messenger.start();

使用箭头函数表达式会使回调与周围的“开始”方法具有相同的含义。 将回调写为标准函数表达式,必须手动安排对其周围环境的访问,例如,将其复制到局部变量中:

class Messenger {  
    message = "Hello World";  
    start() {  
        var _this = this;  
        setTimeout(function() { alert(_this.message); }, 3000);  
    }  
};

var messenger = new Messenger();  
messenger.start();

TypeScript编译器应用这种类型的转换将箭头函数表达式重写为标准函数表达式。

形式的构造

< T > ( ... ) => { ... }

可以被解析为带有类型参数的箭头函数表达式,或者被解析为没有类型参数的箭头函数的类型断言。 它被解析为前者,但是可以使用括号来选择后者的含义:

< T > ( ( ... ) => { ... } )

4.12 Class表达式

TODO:文档类表达式

4.13 属性存取

属性访问使用点表示法或括号表示法。 属性访问表达式始终被归类为引用。

表单的点表示法属性访问

object . name

其中对象是表达式,名称是标识符(可能包括保留字),用于访问给定对象上具有给定名称的属性。 点符号属性访问在编译时按以下方式处理:

  • 如果object是Any类型,则允许使用任何名称,并且属性访问类型是Any。
  • 否则,如果名称表示对象的扩展类型(第3.12节)中的可访问表观属性(第3.11.1节),则属性访问属于该属性的类型。 公共成员始终可以访问,但是类的私有成员和受保护成员的访问权限受到限制,如8.2.2中所述。
  • 否则,属性访问无效,并且发生编译时错误。

表格的方括号符号属性访问

object [ index ]

其中对象和索引为表达式,用于通过给定对象上的索引表达式计算的名称访问属性。括号表示法属性访问在编译时按以下方式处理:

  • 如果index是字符串文字或数字文字,并且对象具有由该文字给出的名称的明显属性(第3.11.1节)(在使用数字文字时转换为字符串表示形式),则属性访问权限为该属性的类型。
  • 否则,如果对象具有明显的数字索引签名,并且索引的类型为Any,Number原语类型或枚举类型,则属性访问属于该索引签名的类型。
  • 否则,如果对象具有明显的字符串索引签名,并且索引的类型为Any,String或Number原语类型或枚举类型,则属性访问属于该索引签名的类型。
  • 否则,如果index为Any类型,String或Number原语类型或枚举类型,则属性访问类型为Any。
  • 否则,属性访问无效,并且发生编译时错误。

TODO:符号

上面的规则意味着,使用带有名称名称表示形式的方括号表示法访问属性时,将强类型化属性。例如:

var type = {  
    name: "boolean",  
    primitive: true  
};

var s = type["name"];       // string  
var b = type["primitive"];  // boolean

元组类型为每个元素分配数字名称,因此,当使用带有数字文字的方括号表示法访问时,将强烈键入元素:

var data: [string, number] = ["five", 5];  
var s = data[0];  // string  
var n = data[1];  // number

4.14 new操作算子

new操作具有以下格式之一:

new C  
new C ( ... )  
new C < ... > ( ... )

其中C是一个表达式。 第一种形式等效于提供一个空的参数列表。 C必须是Any类型或具有一个或多个构造或调用签名的对象类型。 该操作在编译时按以下方式处理:

  • 如果C为Any类型,则允许任何参数列表,且运算结果为Any类型。
  • 如果C具有一个或多个明显的构造签名(第3.11.1节),则以与函数调用相同的方式处理该表达式,但是将构造签名用作用于重载解析的初始候选签名集。 函数调用的结果类型成为操作的结果类型。
  • 如果C没有明显的构造签名而是一个或多个明显的调用签名,则将该表达式作为函数调用进行处理。 如果函数调用的结果不是Void,则会发生编译时错误。 操作结果的类型为任何。

4.15 函数调用

从JavaScript扩展了函数调用,以支持可选的类型参数。

Arguments: ( Modified )
	TypeArgumentsopt ( ArgumentListopt )

函数调用采用以下形式之一:

func ( ... )  
func < ... > ( ... )

其中func是函数类型或Any类型的表达式。 函数表达式后跟一个可选的类型参数列表(第3.6.2节)和一个参数列表。

如果func是Any类型,或者是没有调用或构造签名但是Function接口的子类型的对象类型,则该调用是未类型化的函数调用。 在无类型的函数调用中,不允许使用任何类型的参数,参数表达式可以是任何类型和数字,没有为参数表达式提供上下文类型,并且结果始终是Any类型。

如果func具有明显的调用签名(第3.11.1节),则该调用为类型化函数调用。 TypeScript在类型化函数调用中采用重载解析,以支持具有多个调用签名的函数。 此外,TypeScript可以执行类型参数推断以自动确定通用函数调用中的类型参数。

4.15.1 重载解决方案

函数调用中的重载解析的目的是确保至少一个签名适用,为参数提供上下文类型,并确定函数调用的结果类型,这在多个适用的签名之间可能有所不同。重载解析对函数调用的运行时行为没有影响。由于JavaScript不支持函数重载,因此在运行时重要的是函数的名称。

TODO:描述重载解析中通配符函数类型的使用。

类型化函数调用的编译时处理包括以下步骤:

  • 首先,根据声明顺序,从函数类型中的调用签名构建候选签名列表。对于类和接口,继承的签名被视为以extends子句顺序遵循显式声明的签名。
    • 非通用签名在以下情况下是候选
      • 函数调用没有类型参数,并且
      • 该签名适用于函数调用的参数列表。
    • 泛型签名是在以下情况下不带类型参数的函数调用中的候选者:
      • 每个类型参数的类型推断(第4.15.2节)成功完成,
      • 一旦将推断出的类型参数替换为其关联的类型参数,则签名对于函数调用的参数列表适用。
    • 在以下情况下,泛型签名是带有类型实参的函数调用中的候选者:
      • 签名的类型参数数量与类型参数列表中提供的数量相同,
      • 类型参数满足其约束,并且
      • 一旦将类型参数替换为其关联的类型参数,则签名对于函数调用的参数列表适用。
  • 如果候选签名列表为空,则函数调用为错误。
  • 否则,如果候选列表包含一个或多个签名,且每个参数表达式的类型是每个对应参数类型的子类型,则这些签名中第一个的返回类型将成为函数调用的返回类型。
  • 否则,候选列表中第一个签名的返回类型将成为函数调用的返回类型。

在以下情况下,签名被称为相对于参数列表的适用签名:

  • 参数的数量不少于必需的参数的数量,
  • 参数的数量不大于参数的数量,并且
  • 对于每个自变量表达式e及其对应的参数P,当按P的类型在上下文中键入e时(第4.23节),不会出现错误,并且e的类型可分配给P的类型(第3.11.4节)。

TODO:函数调用中的传播运算符迭代器

4.15.2 类型参数推导

给定签名<T1,T2,…,Tn>(p1:P1,p2:P2,…,pm:Pm),其中每个参数类型P引用零个或多个类型参数T,以及一个参数列表(e1, e2,…,em),类型参数推断的任务是找到一组类型参数A1…An来代替T1…Tn,以使参数列表成为适用的签名。

TODO:更新类型参数推断和重载解析规则。

类型参数推论为每个类型参数产生一组候选类型。给定类型参数T和一组候选类型,实际推断出的类型参数确定如下:

  • 如果候选参数类型的集合为空,则T的推断类型参数为T的约束。
  • 否则,如果至少一个候选类型是所有其他候选类型的超类型,则让C表示第一个此类候选类型的加宽形式(第3.12节)。如果C满足T的约束,则T的推断类型参数为C。否则,T的推断类型参数为T的约束。
  • 否则,如果没有候选类型是所有其他候选类型的超类型,则类型推断将失败,并且不会为T推断类型实参。

为了计算候选类型,参数列表按以下方式处理:

  • 最初,所有推断出的类型参数都被认为是未固定的一组空的候选类型。
  • 从左到右,每个参数表达式e都由其对应的参数类型P进行推论式输入,可能导致某些推断的类型参量变得固定,并对来自该类型的未固定的推断的类型参量进行候选类型推断(第3.11.7节)。计算e至P。

通过类型T推论表达式e的过程与通过类型T推论上下文e的过程相同,但以下情况除外:

  • 如果e中包含的表达式是上下文类型的,则改为推断类型。
  • 当推论性地键入一个函数表达式(第4.10节)并且在该表达式中分配给该参数的类型引用对其进行推理的类型参数时,相应的推理类型实参将变为固定,并且不再对其进行任何候选推理。
  • 如果e是仅包含一个泛型调用签名且不包含其他成员的函数类型的表达式,并且T是仅具有一个非泛型调用签名且不包含其他成员的函数类型的表达式,则对由该类引用的类型参数进行的任何推断T的呼叫签名的参数是固定的,并且e的类型更改为函数类型,并且在T的呼叫签名的上下文中实例化了e的呼叫签名(第3.11.6节)。

一个例子:

function choose<T>(x: T, y: T): T {  
    return Math.random() < 0.5 ? x : y;  
}

var x = choose(10, 20);     // Ok, x of type number  
var y = choose("Five", 5);  // Error

在第一次调用“ choose”时,从“ number”到“ T”有两个推论,每个参数一个。 因此,推断“ T”为“ number”,并且该调用等效于

var x = choose<number>(10, 20);

在对“ choose”的第二次调用中,对第一个参数从类型“ string”到“ T”进行推断,对第二个参数从类型“ number”到“ T”进行推断。 由于“字符串”和“数字”都不是另一个的超类型,因此类型推断将失败。 这又意味着没有适用的签名,并且函数调用是错误。

在这个例子中

function map<T, U>(a: T[], f: (x: T) => U): U[] {  
    var result: U[] = [];  
    for (var i = 0; i < a.length; i++) result.push(f(a[i]));  
    return result;  
}

var names = ["Peter", "Paul", "Mary"];  
var lengths = map(names, s => s.length);

对“ map”的调用中对“ T”和“ U”的推论如下:对于第一个参数,推论从类型“ string []”(类型“ names”)到类型“ T” []”,推断“ T”的“字符串”。 对于第二个参数,箭头表达式’s => s.length’的推论输入会导致’T’变得固定,从而推断出的类型’string’可以用于参数’s’。 然后可以确定箭头表达式的返回类型,并从类型’(s:字符串)=>数字’到类型’(x:T)=> U’进行推断,从而推断’U’的’数字’ '。 因此,对“map”的调用等同于

var lengths = map<string, number>(names, s => s.length);

因此,“长度”的结果类型为“数字[]”。

在这个例子中

function zip<S, T, U>(x: S[], y: T[], combine: (x: S) => (y: T) => U): U[] {  
    var len = Math.max(x.length, y.length);  
    var result: U[] = [];  
    for (var i = 0; i < len; i++) result.push(combine(x[i])(y[i]));  
    return result;  
}

var names = ["Peter", "Paul", "Mary"];  
var ages = [7, 9, 12];  
var pairs = zip(names, ages, s => n => ({ name: s, age: n }));

在对“ zip”的调用中对“ S”,“ T”和“ U”的推论如下:使用前两个参数,对“ S”的“ string”和对“ T”的“ number”进行推论。 。 对于第三个参数,外箭头表达式的推论类型会导致“ S”变得固定,从而可以将推论类型“ string”用于参数“ s”。 当函数表达式被推论类型时,其返回表达式也被推论类型。 因此,推论性地键入了内部箭头函数,从而使“ T”变得固定,从而可以将推论类型“ number”用作参数“ n”。 然后可以确定内部箭头函数的返回类型,这又确定了从外部箭头函数返回的函数的返回类型,并根据类型’(s:string)=>(n:number)进行推断。 => {名称:string; age:number}‘转换为类型’(x:S)=>(y:T)=> R’,从而推断出’{{name:string; 年龄:数字}‘代表’R’。 因此,对“ zip”的调用等同于

var pairs = zip<string, number, { name: string; age: number }>(  
    names, ages, s => n => ({ name: s, age: n }));

因此,“ pairs”的结果类型为“ {{name:string; 年龄:数字} []’。

4.15.3 语法歧义

在Arguments产生中包含类型实参(第4.15节)会引起表达式语法的某些歧义。 例如,语句

f(g<A, B>(7));

可以解释为对带有两个参数“ g <A”和“ B>(7)”的“ f”的调用。 或者,可以将其解释为使用一个参数调用“ f”,这是使用两个类型参数和一个常规参数调用通用函数“ g”。

语法歧义的解决方法如下:在一个可能的标记序列解释为Arguments产生的上下文中,如果标记的初始序列形成语法上正确的TypeArguments产生并且后跟一个’('标记,则该序列 令牌的序列将被处理为Arguments产生,而其他可能的解释将被丢弃,否则,令牌序列将不被视为Arguments产生。

此规则意味着对上面的“ f”的调用被解释为具有一个参数的调用,这是对具有两个类型参数和一个常规参数的通用函数“ g”的调用。 但是,这些陈述

f(g < A, B > 7);  
f(g < A, B > +(7));

都被解释为带有两个参数的对“ f”的调用。

4.16 类型断言

TypeScript扩展了JavaScript表达式语法,使其能够声明表达式的类型:

UnaryExpression: ( Modified )
	…
	< Type > UnaryExpression

类型断言表达式由用<和>括起来的类型以及后跟一元表达式组成。 类型断言表达式纯粹是编译时构造。 类型断言不会在运行时检查,并且不会对生成的JavaScript产生任何影响(因此不会产生运行时成本)。 类型和封闭的<和>只需从生成的代码中删除。

在 e形式的类型断言表达式中,e由T在上下文中键入(第4.23节),并且* e *的结果类型必须可分配给T,或者T必须可分配给扩展形式 结果类型e的取值,否则发生编译时错误。 结果的类型为T。

类型断言在两个方向上检查分配兼容性。 因此,类型断言允许进行可能正确但未知的正确类型转换。 在这个例子中

class Shape { ... }

class Circle extends Shape { ... }

function createShape(kind: string): Shape {  
    if (kind === "circle") return new Circle();  
    ...  
}

var circle = <Circle> createShape("circle");

类型注释指示’createShape’函数可能返回’Circle’(因为’Circle’是’Shape’的子类型),但不知道这样做(因为其返回类型为’Shape’)。 因此,需要类型断言来将结果视为“圆”。

如上所述,在运行时不检查类型断言,并且程序员有责任防止错误,例如使用instanceof运算符:

var shape = createShape(shapeKind);  
if (shape instanceof Circle) {  
    var circle = <Circle> shape;  
    ...  
}

TODO:操作算子

4.17 JSX表达式

TODO:JSX表达式

4.18 一元运算符

接下来的小节指定一元运算符的编译时处理规则。 通常,如果一元运算符的操作数不满足上述要求,则会发生编译时错误,并且在进一步处理中,运算结果默认为类型Any。

4.18.1 ++和–运算符

这些运算符以前缀或后缀形式要求其操作数的类型为Any,Number原语类型或枚举类型,并分类为引用(第4.1节)。 它们产生Number原语类型的结果。

4.18.2 +,-和~操作符

这些运算符允许其操作数为任何类型,并产生Number原语类型的结果。

一元+运算符可以方便地用于将任何类型的值转换为Number原语类型:

function getValue() { ... }

var n = +getValue();

上面的示例将“ getValue()”的结果转换为数字(如果还不是数字)。 不管’getValue’的返回类型如何,推断为’n’的类型都是Number原语类型。

4.18.3 !操作符

! 运算符允许其操作数为任何类型,并产生布尔基元类型的结果。

两个一元! 顺序操作符可以方便地用于将任何类型的值转换为Boolean基本类型:

function getValue() { ... }

var b = !!getValue();

上面的示例将’getValue()'的结果转换为布尔值(如果还不是布尔值的话)。 不论’getValue’的返回类型如何,为’b’推断的类型都是布尔类型的原始类型。

4.18.4 delete操作符

delete运算符采用任何类型的操作数,并产生布尔基元类型的结果。

4.18.5 void操作符

“ void”运算符采用任何类型的操作数,并产生值“ undefined”。 结果的类型为未定义类型(3.2.7)。

4.18.6 typeof操作符

“ typeof”运算符采用任何类型的操作数,并产生String基本类型的值。 在需要类型的位置,也可以在类型查询中使用“ typeof”(第3.8.10节)以生成表达式的类型。

var x = 5;  
var y = typeof x;  // Use in an expression  
var z: typeof x;   // Use in a type query

在上面的示例中,“ x”的类型为“数字”,“ y”的类型为“字符串”,因为在表达式中使用时,“ typeof”会生成字符串类型的值(在这种情况下为字符串“ number”) ,而’z’是’number’类型的,因为在类型查询中使用’typeof’可以获取表达式的类型。

4.19 二元操作符

接下来的小节指定了二进制运算符的编译时处理规则。 通常,如果二进制运算符的操作数不满足规定的要求,则会发生编译时错误,并且该操作的结果默认为在进一步处理中键入any。 提供了一些表格,这些表格总结了Any类型,Boolean,Number和String原语类型以及所有其他类型(表中的Other列)的操作数的编译时处理规则。

4.19.1 *, /, %, –, <<, >>, >>>, &, ^, 和 | 等操作符

这些运算符要求其操作数的类型为Any,Number基本类型或枚举类型。 枚举类型的操作数被视为具有基本类型Number。 如果一个操作数为空值或未定义值,则将其视为具有另一种操作数的类型。 结果始终是Number原语类型。

AnyBooleanNumberStringOther
AnyNumberNumber
Boolean
NumberNumberNumber
String
Ohter

TODO:指数表达式

4.19.2 +操作符

二进制+运算符要求两个操作数都为Number原语类型或枚举类型,或至少一个操作数为Any类型或String原语类型。 枚举类型的操作数被视为具有基本类型Number。 如果一个操作数为空值或未定义值,则将其视为具有另一种操作数的类型。 如果两个操作数均为Number原语类型,则结果为Number原语类型。 如果一个或两个操作数均为String基本类型,则结果为String基本类型。 否则,结果的类型为Any。

AnyBooleanNumberStringOther
AnyAnyAnyAnyStringAny
BooleanAnyString
NumberAnyNumberString
StringStringStringStringStringString
OhterAnyString

通过添加空字符串,可以将任何类型的值转换为String基本类型:

function getValue() { ... }

var s = getValue() + "";

上面的示例将’getValue()'的结果转换为字符串(如果还不是字符串的话)。 不管返回类型为“ getValue”,为“ s”推断的类型都是String原语类型。

4.19.3 <, >, <=, >=, ==, !=, ===, 和 !== 操作符

这些运算符要求将一种或两种操作数类型分配给另一种。 结果始终是布尔基元类型。

AnyBooleanNumberStringOther
AnyBooleanBooleanBooleanBooleanBoolean
BooleanBooleanBoolean
NumberBooleanBoolean
StringBooleanBoolean
OhterBooleanBoolean

4.19.4 instanceof操作符

instanceof运算符要求左侧操作数的类型为Any,对象类型或类型参数类型,而右侧操作数的类型为Any类型或“函数”接口类型的子类型。 结果始终是布尔基元类型。

请注意,包含一个或多个调用或构造签名的对象类型将自动成为“函数”接口类型的子类型,如3.3节所述。

4.19.5 in操作符

in运算符要求左操作数的类型为Any,String原语类型或Number原语类型,而右操作数的类型为Any,对象类型或类型参数类型。 结果始终是布尔基元类型。

4.19.6 &&操作符

&&运算符允许操作数为任何类型,并产生与第二个操作数相同类型的结果。

AnyBooleanNumberStringOther
AnyAnyBooleanNumberStringOther
BooleanAnyBooleanNumberStringOther
NumberAnyBooleanNumberStringOther
StringAnyBooleanNumberStringOther
OhterAnyBooleanNumberStringOther

4.19.7 ||操作符

|| 运算符允许操作数为任何类型。

如果|| expression是上下文类型的(第4.23节),操作数也使用相同类型的上下文类型。 否则,将不会在上下文中键入左操作数,而在上下文中将根据左操作数的类型来键入右操作数。

结果的类型是两种操作数类型的并集类型。

AnyBooleanNumberStringOther
AnyAnyAnyAnyAnyAny
BooleanAnyBooleanNBS
NumberAnyNBNumberS
StringAnySBSN
OhterAnyBONO

4.20 Conditional操作符

在形式的条件表达式中

test ? expr1 : expr2

测试表达式可以是任何类型。

如果条件表达式是上下文类型的(第4.23节),则expr1和expr2会使用相同类型的上下文类型。 否则,不会在上下文中键入expr1和expr2。

结果的类型是expr1和expr2类型的并集类型。

4.21 Assignment操作符

赋值的形式

v = expr

要求将v分类为参考(第4.1节)或分配模式(第4.21.1节)。 根据v的类型在上下文中键入expr表达式(第4.23节),并且必须将expr的类型分配给v的类型(第3.11.4节),否则会发生编译时错误。 结果是具有expr类型的值。

形式的复合赋值

v ??= expr

其中?? =是复合赋值运算符之一

*=   /=   %=   +=   -=   <<=   >>=   >>>=   &=   ^=   |=

与相应的非复合运算符具有相同的要求,并产生相同类型的值。 复合分配还需要将v分类为参考(第4.1节),并且非复合运算的类型可以分配给v的类型。请注意,在复合分配中,不允许v是分配模式。

4.21.1 销毁赋值

解构分配是一种赋值操作,其中左侧操作数是由ECMAScript 2015规范中的AssignmentPattern生产定义的解构分配模式。

在解构分配表达式中,右侧的表达式类型必须可分配给左侧的分配目标。如果满足以下条件之一,则认为类型S的表达式可分配给分配目标V:

  • V是变量,并且S可分配给V的类型。
  • V是对象分配模式,对于V中的每个分配属性P,
    • S是任何类型,或
    • S具有一个明显的属性,该属性具有在P中指定的属性名称,该属性名称的类型可以分配给P中给定的目标,或者
    • P指定数字属性名称,而S具有数字索引签名,该数字索引签名的类型可分配给P中给定的目标,或者
    • S具有可分配给P中给定目标的类型的字符串索引签名。
  • V是数组分配模式,S是Any类型或类似数组的类型(第3.3.2节),对于V中的每个分配元素E,
    • S是任何类型,或
    • S是类似元组的类型(第3.3.3节),具有名为N的属性,该属性的类型可分配给E中给定的目标,其中N是数组分配模式中E的数字索引,或者
    • S不是类似元组的类型,并且S的数字索引签名类型可分配给E中给定的目标。

TODO:当赋值元素E是rest元素时更新以指定行为

在包含默认值的分配属性或元素中,默认值的类型必须可分配给分配属性或元素中给定的目标。

当输出目标是ECMAScript 2015或更高版本时,解构变量分配在生成的JavaScript代码中保持不变。当输出目标是ECMAScript 3或5时,解构变量分配将重写为一系列简单分配。例如,销毁分配

var x = 1;  
var y = 2;  
[x, y] = [y, x];

被重写为简单的变量分配

var x = 1;  
var y = 2;  
_a = [y, x], x = _a[0], y = _a[1];  
var _a;

4.22 逗号运算符

逗号运算符允许操作数为任何类型,并产生与第二个操作数相同类型的结果。

4.23 上下文类型的表达式

通过将表达式计算出的值的目标类型考虑在内,可以在多种情况下改进表达式的类型检查。在这种情况下,可以说表达式是根据目的地的类型在上下文中键入的。在以下情况下,将在上下文中键入表达式:

  • 在变量,参数,绑定属性,绑定元素或成员声明中,初始化器表达式由上下文类型为
    • 声明的类型注释中给出的类型(如果有),否则
    • 对于参数,由上下文签名提供的类型(第4.10节)(如果有),否则
    • 声明中绑定模式所隐含的类型(第5.2.3节)(如果有)。
  • 在具有返回类型批注的函数声明,函数表达式,箭头函数,方法声明或get访问器声明的主体中,返回表达式根据返回类型批注中给出的类型在上下文中键入。
  • 在没有返回类型批注的函数表达式或箭头函数的主体中,如果函数表达式或箭头函数是由具有一个唯一调用签名的函数类型在上下文中键入的,并且如果该调用签名是非泛型的,则返回表达式为根据该调用签名的返回类型在上下文中进行键入。
  • 在构造函数声明的主体中,返回表达式由包含的类类型在上下文中键入。
  • 在没有返回类型批注的get访问器的主体中,如果存在匹配的集合访问器且该集合访问器具有参数类型批注,则返回表达式将根据集合访问器的参数类型批注中给出的类型在上下文中进行键入。
  • 在类型化的函数调用中,参数表达式通过其对应的参数类型在上下文中进行类型化。
  • 在上下文类型的对象文字中,每个属性值表达式的上下文类型为
    • 上下文类型中具有匹配名称的属性的类型(如果有),否则
    • 对于以数字命名的属性,则为上下文类型的数字索引类型(如果有),否则
    • 上下文类型的字符串索引类型(如果有)。
  • 在不包含扩展元素的上下文类型的数组文字表达式中,索引N处的元素表达式由
    • 上下文类型中数字名称为N的属性的类型(如果有),否则
    • 上下文类型的数字索引类型(如果有)。
  • 在包含一个或多个散布元素的上下文类型的数组文字表达式中,索引N处的元素表达式由上下文类型的数字索引类型(如果有)在上下文中进行类型化。
  • 在上下文类型的带括号的表达式中,所包含的表达式在上下文上使用相同的类型。
  • 在类型断言中,表达式由指示的类型在上下文中进行类型化。
  • 在||中运算符表达式,如果表达式是上下文类型的,则操作数将使用相同类型的上下文类型。否则,将根据左表达式的类型在上下文中键入右表达式。
  • 在上下文类型的条件运算符表达式中,操作数由同一类型在上下文中类型。
  • 在赋值表达式中,右手表达式由左手表达式的类型在上下文中键入。

在下面的例子中

interface EventObject {  
    x: number;  
    y: number;  
}

interface EventHandlers {  
    mousedown?: (event: EventObject) => void;  
    mouseup?: (event: EventObject) => void;  
    mousemove?: (event: EventObject) => void;  
}

function setEventHandlers(handlers: EventHandlers) { ... }

setEventHandlers({  
    mousedown: e => { startTracking(e.x, e.y); },  
    mouseup: e => { endTracking(); }  
});

传递给“ setEventHandlers”的对象文字在上下文中键入为“ EventHandlers”类型。 这导致这两个属性分配在上下文中被键入为未命名的函数类型’(event:EventObject)=> void’,这又导致箭头函数表达式中的’e’参数被自动键入为’EventObject’。

4.24 类型保护

类型保护是涉及“ typeof”和“ instanceof”运算符的特定表达模式,这些运算符会使变量或参数的类型缩小为更具体的类型。 例如,在下面的代码中,了解’x’的静态类型以及’typeof’检查的组合,可以很安全地将’x’的类型缩小为’if’语句和数字的第一分支中的字符串 在“ if”语句的第二个分支中。

function foo(x: number | string) {  
    if (typeof x === "string") {  
        return x.length;  // x has type string here  
    }  
    else {  
        return x + 1;     // x has type number here  
    }  
}

在以下情况下,变量或参数的类型变窄:

  • 在’if’语句的true分支语句中,如果条件为true,则在’if’条件下通过类型保护缩小变量或参数的类型,前提是’if’语句的任何部分都不包含对变量或参数的赋值。
  • 在’if’语句的false分支语句中,如果为false,则在’if’条件下由类型保护缩小变量或参数的类型,前提是’if’语句的任何部分都不包含对变量或参数的赋值。
  • 在条件表达式的true表达式中,条件为true时,类型保护将缩小变量或参数的类型,前提是条件表达式的任何部分都不包含对变量或参数的赋值。
  • 在条件表达式的false表达式中,条件为false时,由类型保护缩小变量或参数的类型,条件是条件表达式的任何部分都不包含对变量或参数的赋值。
  • 在&&操作的右侧操作数中,如果为true,则变量或参数的类型将由左侧操作数中的类型保护范围缩小,前提是两个操作数均不包含对变量或参数的赋值。
  • 在||的右操作数中如果操作为false,则变量或参数的类型将通过左侧操作数中的类型防护缩小,前提是两个操作数都不包含对变量或参数的赋值。

类型保护只是一个遵循特定模式的表达式。当类型为true或false时,通过类型保护将变量x的类型缩小的过程取决于类型保护,如下所示:

  • 类型为C instance x C的类型保护,其中x不是Any类型,C是全局类型’Function’的子类型,并且C具有名为’prototype’的属性
    • 如果为true,则将x的类型缩小为C中的’prototype’属性的类型,条件是它是x的子类型,或者,如果x的类型为联合类型,则从x的类型中删除所有组成部分不是C语言中’prototype’属性类型的子类型的类型,或者
    • 如果为false,则对x的类型没有影响。
  • 类型为x === s的类型保护,其中s是值为’string’,'number’或’boolean’的字符串文字,
    • 如果为true,则将x的类型缩小到给定的原始类型,前提是它是x的子类型,或者,如果x的类型是联合类型,则从x的类型中删除所有不是子类型的构成类型给定原始类型的,或
    • 如果为false,则从x的类型中删除基本类型。
  • 一个类型为x === s的类型保护,其中s是一个字符串文字,除了’string’,'number’或’boolean’以外的任何值,
    • 为true时,如果x是联合类型,则从x的类型中删除所有构成类型,它们是字符串,数字或布尔型基本类型的子类型,或者
    • 如果为false,则对x的类型没有影响。
  • 类型为x !== s的类型保护,其中s是字符串文字,
    • 如果为true,则为false时将x的类型缩小为typeof x === s,或者
    • 如果为false,则为true时,通过typeof x === s缩小x的类型。
  • !expr形式的类型防护
    • 如果为true,则为false时通过expr缩小x的类型,或者
    • 如果为false,则为true,通过expr缩小x的类型。
  • 形式为expr1 && expr2的类型防护
    • 如果为true,则为true时通过expr1缩小x的类型,然后为true时通过expr2缩小x的类型,或者
    • 如果为false,则将x的类型缩小为T1 | T2,其中T1是在false时由expr1缩小的x的类型,而T2是在true时由expr1缩小的x的类型,而在false时由expr2缩小的x的类型。
  • 形式为expr1 ||的类型防护expr2
    • 如果为true,则将x的类型缩小为T1 | T2,其中T1是为true时由expr1缩小的x的类型,T2为为false时由expr1缩小的x的类型,为true时由expr2缩小的x的类型,或
    • 如果为false,则在false时通过expr1缩小x的类型,然后在false时通过expr2缩小x的类型。
  • 任何其他形式的类型保护都不会影响x的类型。

在上述规则中,当缩小操作将所有类型从联合类型中删除时,该操作对联合类型没有影响。

请注意,类型防护仅影响变量和参数的类型,而对诸如属性之类的对象成员没有影响。另请注意,可以通过调用更改受保护变量类型的函数来取消类型保护。

待办事项:用户定义的类型保护功能

在这个例子中

function isLongString(obj: any) {  
    return typeof obj === "string" && obj.length > 100;  
}

obj参数在&&运算符的右侧操作数中具有字符串类型。

在这个例子中

function processValue(value: number | (() => number)) {  
    var x = typeof value !== "number" ? value() : value;  
    // Process number in x  
}

value参数在第一个条件表达式中具有type()=> number,在第二个条件表达式中具有type number,并且x的推断类型为number。

在这个例子中

function f(x: string | number | boolean) {  
    if (typeof x === "string" || typeof x === "number") {  
        var y = x;  // Type of y is string | number  
    }  
    else {  
        var z = x;  // Type of z is boolean  
    }  
}

x的类型是字符串| 编号 ||的左操作数中的布尔值 运算符,数字| ||的右操作数中的布尔值 运算符,字符串| if语句的第一分支中的数字,而if语句的第二分支中的布尔值。

在这个例子中

class C {  
    data: string | string[];  
    getData() {  
        var data = this.data;  
        return typeof data === "string" ? data : data.join(" ");  
    }  
}

数据变量的类型在第一个条件表达式中为string,在第二个条件表达式中为string [],而getData的推断类型为string。 请注意,必须将data属性复制到本地变量,以使类型保护生效。

在这个例子中

class NamedItem {  
    name: string;  
}

function getName(obj: Object) {  
    return obj instanceof NamedItem ? obj.name : "unknown";  
}

在第一个条件表达式中,obj的类型缩小为NamedItem,并且getName函数的推断类型为字符串。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值