一、TypeScript 是什么
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:
1.1 TypeScript 与 JavaScript 的区别
TypeScriptJavaScriptJavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误强类型,支持静态和动态类型弱类型,没有静态类型选项最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用支持模块、泛型和接口不支持模块,泛型或接口社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持
1.2 获取 TypeScript命令行的 TypeScript 编译器可以使用 npm 包管理器来安装。
1.安装 TypeScript
$npminstall-gtypescript
$tsc-v
#Version4.0.2
$tschelloworld.ts
#helloworld.ts=>helloworld.js
当然,对刚入门 TypeScript 的小伙伴来说,也可以不用安装 typescript,而是直接使用线上的 TypeScript Playground 来学习新的语法或新特性。通过配置 TS Config 的 Target,可以设置不同的编译目标,从而编译生成不同的目标代码。
下图示例中所设置的编译目标是 ES5:
(图片来源:https://www.typescriptlang.org/play)
1.3 典型 TypeScript 工作流程
如你所见,在上图中包含 3 个 ts 文件:a.ts、b.ts 和 c.ts。这些文件将被 TypeScript 编译器,根据配置的编译选项编译成 3 个 js 文件,即 a.js、b.js 和 c.js。对于大多数使用 TypeScript 开发的 Web 项目,我们还会对编译生成的 js 文件进行打包处理,然后在进行部署。
1.4 TypeScript 初体验新建一个 hello.ts 文件,并输入以下内容:
functiongreet(person:string){
return'Hello,' person;
}
console.log(greet("TypeScript"));
然后执行 tsc hello.ts 命令,之后会生成一个编译好的文件 hello.js:
"usestrict";
functiongreet(person){
return'Hello,' person;
}
console.log(greet("TypeScript"));
观察以上编译后的输出结果,我们发现 person 参数的类型信息在编译后被擦除了。TypeScript 只会在编译阶段对类型进行静态检查,如果发现有错误,编译时就会报错。而在运行时,编译生成的 JS 与普通的 JavaScript 文件一样,并不会进行类型检查。
二、TypeScript 基础类型2.1 boolean 类型
letisDone:boolean=false;
// ES5:var isDone = false;
letcount:number=10;
// ES5:var count = 10;
letname:string="semliker";
// ES5:var name ='semlinker';
constsym=Symbol();
letobj={
[sym]:"semlinker",
};
console.log(obj[sym]);//semlinker
letlist:number[]=[1,2,3];
// ES5:var list =[1,2,3];
letlist:Array<number>=[1,2,3];//Array<number>泛型语法
// ES5:var list =[1,2,3];
使用枚举我们可以定义一些带名字的常量。使用枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript 支持数字的和基于字符串的枚举。
1.数字枚举
enumDirection{
NORTH,
SOUTH,
EAST,
WEST,
}
letdir:Direction=Direction.NORTH;
默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。
以上的枚举示例经编译后,对应的 ES5 代码如下:
"usestrict";
varDirection;
(function(Direction){
Direction[(Direction["NORTH"]=0)]="NORTH";
Direction[(Direction["SOUTH"]=1)]="SOUTH";
Direction[(Direction["EAST"]=2)]="EAST";
Direction[(Direction["WEST"]=3)]="WEST";
})(Direction||(Direction={}));
vardir=Direction.NORTH;
当然我们也可以设置 NORTH 的初始值,比如:
enumDirection{
NORTH=3,
SOUTH,
EAST,
WEST,
}
在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enumDirection{
NORTH="NORTH",
SOUTH="SOUTH",
EAST="EAST",
WEST="WEST",
}
以上代码对应的 ES5 代码如下:
"usestrict";
varDirection;
(function(Direction){
Direction["NORTH"]="NORTH";
Direction["SOUTH"]="SOUTH";
Direction["EAST"]="EAST";
Direction["WEST"]="WEST";
})(Direction||(Direction={}));
通过观察数字枚举和字符串枚举的编译结果,我们可以知道数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:
enumDirection{
NORTH,
SOUTH,
EAST,
WEST,
}
letdirName=Direction[0];//NORTH
letdirVal=Direction["NORTH"];//0
另外,对于纯字符串枚举,我们不能省略任何初始化程序。而数字枚举如果没有显式设置值时,则会使用默认规则进行初始化。
3.常量枚举除了数字枚举和字符串枚举之外,还有一种特殊的枚举 —— 常量枚举。它是使用 const 关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生成任何 JavaScript。为了更好地理解这句话,我们来看一个具体的例子:
constenumDirection{
NORTH,
SOUTH,
EAST,
WEST,
}
letdir:Direction=Direction.NORTH;
以上代码对应的 ES5 代码如下:
"usestrict";
vardir=0/*NORTH*/;
异构枚举的成员值是数字和字符串的混合:
enumEnum{
A,
B,
C="C",
D="D",
E=8,
F,
}
以上代码对于的 ES5 代码如下:
"usestrict";
varEnum;
(function(Enum){
Enum[Enum["A"]=0]="A";
Enum[Enum["B"]=1]="B";
Enum["C"]="C";
Enum["D"]="D";
Enum[Enum["E"]=8]="E";
Enum[Enum["F"]=9]="F";
})(Enum||(Enum={}));
通过观察上述生成的 ES5 代码,我们可以发现数字枚举相对字符串枚举多了 “反向映射”:
console.log(Enum.A)//输出:0
console.log(Enum[0])//输出:A
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。
letnotSure:any=666;
notSure="semlinker";
notSure=false;
any 类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。比如:
letvalue:any;
value.foo.bar;//OK
value.trim();//OK
value();//OK
newvalue();//OK
value[0][1];//OK
在许多场景下,这太宽松了。使用 any 类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。
2.8 Unknown 类型就像所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)。下面我们来看一下 unknown 类型的使用示例:
letvalue:unknown;
value=true;//OK
value=42;//OK
value="HelloWorld";//OK
value=[];//OK
value={};//OK
value=Math.random;//OK
value=null;//OK
value=undefined;//OK
value=newTypeError();//OK
value=Symbol("type");//OK
对 value 变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发生什么?
letvalue:unknown;
letvalue1:unknown=value;//OK
letvalue2:any=value;//OK
letvalue3:boolean=value;//Error
letvalue4:number=value;//Error
letvalue5:string=value;//Error
letvalue6:Object=value;//Error
letvalue7:any[]=value;//Error
letvalue8:Function=value;//Error
unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。
现在让我们看看当我们尝试对类型为 unknown 的值执行操作时会发生什么。以下是我们在之前 any 章节看过的相同操作:
letvalue:unknown;
value.foo.bar;//Error
value.trim();//Error
value();//Error
newvalue();//Error
value[0][1];//Error
将 value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。
2.9 Tuple 类型众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。
元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看一个具体的例子:
lettupleType:[string,boolean];
tupleType=["semlinker",true];
在上面代码中,我们定义了一个名为 tupleType 的变量,它的类型是一个类型数组 [string, boolean],然后我们按照正确的类型依次初始化 tupleType 变量。与数组一样,我们可以通过下标来访问元组中的元素:
console.log(tupleType[0]);//semlinker
console.log(tupleType[1]);//true
在元组初始化的时候,如果出现类型不匹配的话,比如:
tupleType=[true,"semlinker"];
此时,TypeScript 编译器会提示以下错误信息:
[0]:Type'true'isnotassignabletotype'string'.
[1]:Type'string'isnotassignabletotype'boolean'.
很明显是因为类型不匹配导致的。在元组初始化的时候,我们还必须提供每个属性的值,不然也会出现错误,比如:
tupleType=["semlinker"];
此时,TypeScript 编译器会提示以下错误信息:
Property'1'ismissingintype'[string]'butrequiredintype'[string,boolean]'.
某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:
//声明函数返回值为void
functionwarnUser():void{
console.log("Thisismywarningmessage");
}
以上代码编译生成的 ES5 代码如下:
"usestrict";
functionwarnUser(){
console.log("Thisismywarningmessage");
}
需要注意的是,声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined 或 null:
letunusable:void=undefined;
TypeScript 里,undefined 和 null 两者有各自的类型分别为 undefined 和 null。
letu:undefined=undefined;
letn:null=null;
默认情况下 null 和 undefined 是所有类型的子类型。就是说你可以把 null 和 undefined 赋值给 number 类型的变量。然而,如果你指定了--strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自的类型。
2.12 object, Object 和 {} 类型1.object 类型object 类型是:TypeScript 2.2 引入的新类型,它用于表示非原始类型。
//node_modules/typescript/lib/lib.es5.d.ts
interfaceObjectConstructor{
create(o:object|null):any;
//...
}
constproto={};
Object.create(proto);//OK
Object.create(null);//OK
Object.create(undefined);//Error
Object.create(1337);//Error
Object.create(true);//Error
Object.create("oops");//Error
Object 类型:它是所有 Object 类的实例的类型,它由以下两个接口来定义:
- Object 接口定义了 Object.prototype 原型对象上的属性;
//node_modules/typescript/lib/lib.es5.d.ts
interfaceObject{
constructor:Function;
toString():string;
toLocaleString():string;
valueOf():Object;
hasOwnProperty(v:PropertyKey):boolean;
isPrototypeOf(v:Object):boolean;
propertyIsEnumerable(v:PropertyKey):boolean;
}
- ObjectConstructor 接口定义了 Object 类的属性。
//node_modules/typescript/lib/lib.es5.d.ts
interfaceObjectConstructor{
/**Invocationvia`new`*/
new(value?:any):Object;
/**Invocationviafunctioncalls*/
(value?:any):any;
readonlyprototype:Object;
getPrototypeOf(o:any):any;
//···
}
declarevarObject:ObjectConstructor;
Object 类的所有实例都继承了 Object 接口中的所有属性。
3.{} 类型{} 类型描述了一个没有成员的对象。当你试图访问这样一个对象的任意属性时,TypeScript 会产生一个编译时错误。
//Type{}
constobj={};
//Error:Property'prop'doesnotexistontype'{}'.
obj.prop="semlinker";
但是,你仍然可以使用在 Object 类型上定义的所有属性和方法,这些属性和方法可通过 JavaScript 的原型链隐式地使用:
//Type{}
constobj={};
//"[objectObject]"
obj.toString();
never 类型表示的是那些永不存在的值的类型。例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
//返回never的函数必须存在无法达到的终点
functionerror(message:string):never{
thrownewError(message);
}
functioninfiniteLoop():never{
while(true){}
}
在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:
typeFoo=string|number;
functioncontrolFlowAnalysisWithNever(foo:Foo){
if(typeoffoo==="string"){
//这里foo被收窄为string类型
}elseif(typeoffoo==="number"){
//这里foo被收窄为number类型
}else{
//foo在这里是never
constcheck:never=foo;
}
}
注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:
typeFoo=string|number|boolean;
然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保
controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。
三、TypeScript 断言3.1 类型断言有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
类型断言有两种形式:
1.“尖括号” 语法
letsomeValue:any="thisisastring";
letstrLength:number=(<string>someValue).length;
letsomeValue:any="thisisastring";
letstrLength:number=(someValueasstring).length;
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。
那么非空断言操作符到底有什么用呢?下面我们先来看一下非空断言操作符的一些使用场景。
1.忽略 undefined 和 null 类型
functionmyFunc(maybeString:string|undefined|null){
//Type'string|null|undefined'isnotassignabletotype'string'.
//Type'undefined'isnotassignabletotype'string'.
constonlyString:string=maybeString;//Error
constignoreUndefinedAndNull:string=maybeString!;//Ok
}
typeNumGenerator=()=>number;
functionmyFunc(numGenerator:NumGenerator|undefined){
//Objectispossibly'undefined'.(2532)
//Cannotinvokeanobjectwhichispossibly'undefined'.(2722)
constnum1=numGenerator();//Error
constnum2=numGenerator!();//OK
}
因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。比如下面这个例子:
consta:number|undefined=undefined;
constb:number=a!;
console.log(b);
以上 TS 代码会编译生成以下 ES5 代码:
"usestrict";
consta=undefined;
constb=a;
console.log(b);
虽然在 TS 代码中,我们使用了非空断言,使得 const b: number = a!; 语句可以通过 TypeScript 类型检查器的检查。但在生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined。
3.3 确定赋值断言在 TypeScript 2.7 版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:
letx:number;
initialize();
//Variable'x'isusedbeforebeingassigned.(2454)
console.log(2*x);//Error
functioninitialize(){
x=10;
}
很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:
letx!:number;
initialize();
console.log(2*x);//Ok
functioninitialize(){
x=10;
}
通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。
四、类型守卫类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:
4.1 in 关键字
interfaceAdmin{
name:string;
privileges:string[];
}
interfaceEmployee{
name:string;
startDate:Date;
}
typeUnknownEmployee=Employee|Admin;
functionprintEmployeeInformation(emp:UnknownEmployee){
console.log("Name:" emp.name);
if("privileges"inemp){
console.log("Privileges:" emp.privileges);
}
if("startDate"inemp){
console.log("StartDate:" emp.startDate);
}
}
functionpadLeft(value:string,padding:string|number){
if(typeofpadding==="number"){
returnArray(padding 1).join("") value;
}
if(typeofpadding==="string"){
returnpadding value;
}
thrownewError(`Expectedstringornumber,got'${padding}'.`);
}
typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
4.3 instanceof 关键字
interfacePadder{
getPaddingString():string;
}
classSpaceRepeatingPadderimplementsPadder{
constructor(privatenumSpaces:number){}
getPaddingString(){
returnArray(this.numSpaces 1).join("");
}
}
classStringPadderimplementsPadder{
constructor(privatevalue:string){}
getPaddingString(){
returnthis.value;
}
}
letpadder:Padder=newSpaceRepeatingPadder(6);
if(padderinstanceofSpaceRepeatingPadder){
//padder的类型收窄为'SpaceRepeatingPadder'
}
functionisNumber(x:any):xisnumber{
returntypeofx==="number";
}
functionisString(x:any):xisstring{
returntypeofx==="string";
}
联合类型通常与 null 或 undefined 一起使用:
constsayHello=(name:string|undefined)=>{
/*...*/
};
例如,这里 name 的类型是 string | undefined 意味着可以将 string 或 undefined 的值传递给sayHello 函数。
sayHello("semlinker");
sayHello(undefined);
通过这个示例,你可以凭直觉知道类型 A 和类型 B 联合后的类型是同时接受 A 和 B 值的类型。此外,对于联合类型来说,你可能会遇到以下的用法:
letnum:1|2=1;
typeEventNames='click'|'scroll'|'mousemove';
以上示例中的 1、2 或 'click' 被称为字面量类型,用来约束取值只能是某几个值中的一个。
5.2 可辨识联合TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
1.可辨识可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:
enumCarTransmission{
Automatic=200,
Manual=300
}
interfaceMotorcycle{
vType:"motorcycle";//discriminant
make:number;//year
}
interfaceCar{
vType:"car";//discriminant
transmission:CarTransmission
}
interfaceTruck{
vType:"truck";//discriminant
capacity:number;//intons
}
在上述代码中,我们分别定义了 Motorcycle、 Car 和 Truck 三个接口,在这些接口中都包含一个 vType 属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
2.联合类型基于前面定义了三个接口,我们可以创建一个 vehicle 联合类型:
typeVehicle=Motorcycle|Car|Truck;
现在我们就可以开始使用 Vehicle 联合类型,对于 Vehicle 类型的变量,它可以表示不同类型的车辆。
3.类型守卫下面我们来定义一个 evaluatePrice 方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下:
constEVALUATION_FACTOR=Math.PI;
functionevaluatePrice(vehicle:Vehicle){
returnvehicle.capacity*EVALUATION_FACTOR;
}
constmyTruck:Truck={vType:"truck",capacity:9.5};
evaluatePrice(myTruck);
对于以上代码,TypeScript 编译器将会提示以下错误信息:
Property'capacity'doesnotexistontype'Vehicle'.
Property'capacity'doesnotexistontype'Motorcycle'.
原因是在 Motorcycle 接口中,并不存在 capacity 属性,而对于 Car 接口来说,它也不存在 capacity 属性。那么,现在我们应该如何解决以上问题呢?这时,我们可以使用类型守卫。下面我们来重构一下前面定义的 evaluatePrice 方法,重构后的代码如下:
functionevaluatePrice(vehicle:Vehicle){
switch(vehicle.vType){
case"car":
returnvehicle.transmission*EVALUATION_FACTOR;
case"truck":
returnvehicle.capacity*EVALUATION_FACTOR;
case"motorcycle":
returnvehicle.make*EVALUATION_FACTOR;
}
}
在以上代码中,我们使用 switch 和 case 运算符来实现类型守卫,从而确保在 evaluatePrice 方法中,我们可以安全地访问 vehicle 对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。
5.3 类型别名类型别名用来给一个类型起个新名字。
typeMessage=string|string[];
letgreet=(message:Message)=>{
//...
};
在 TypeScript 中交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
typePartialPointX={x:number;};
typePoint=PartialPointX&{y:number;};
letpoint:Point={
x:1,
y:1
}
在上面代码中我们先定义了 PartialPointX 类型,接着使用 & 运算符创建一个新的 Point 类型,表示一个含有 x 和 y 坐标的点,然后定义了一个 Point 类型的变量并初始化。
6.1 同名基础类型属性的合并那么现在问题来了,假设在合并多个类型的过程中,刚好出现某些类型存在相同的成员,但对应的类型又不一致,比如:
interfaceX{
c:string;
d:string;
}
interfaceY{
c:number;
e:string
}
typeXY=X&Y;
typeYX=Y&X;
letp:XY;
letq:YX;
在上面的代码中,接口 X 和接口 Y 都含有一个相同的成员 c,但它们的类型不一致。对于这种情况,此时 XY 类型或 YX 类型中成员 c 的类型是不是可以是 string 或 number 类型呢?比如下面的例子:
p={c:6,d:"d",e:"e"};
q={c:"c",d:"d",e:"e"};
为什么接口 X 和接口 Y 混入后,成员 c 的类型会变成 never 呢?这是因为混入后成员 c 的类型为 string & number,即成员 c 的类型既可以是 string 类型又可以是 number 类型。很明显这种类型是不存在的,所以混入后成员 c 的类型为 never。
6.2 同名非基础类型属性的合并在上面示例中,刚好接口 X 和接口 Y 中内部成员 c 的类型都是基本数据类型,那么如果是非基本数据类型的话,又会是什么情形。我们来看个具体的例子:
interfaceD{d:boolean;}
interfaceE{e:string;}
interfaceF{f:number;}
interfaceA{x:D;}
interfaceB{x:E;}
interfaceC{x:F;}
typeABC=A&B&C;
letabc:ABC={
x:{
d:true,
e:'semlinker',
f:666
}
};
console.log('abc:',abc);
以上代码成功运行后,控制台会输出以下结果:
由上图可知,在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。
,