目录
typescript的核心就是类型检查,类型的定义、类型的使用、给变量赋值之类的;typescript的基础建立在javascript,typescript需要经过编译,将typescript文件编译成javascript文件,真正用于运行在项目中也是javascript文件,但是因为平时用的JavaScript是弱类型语言比较容易出bug,所以typescript的出现可以稍微避免这样的问题以让项目使用更健全的代码;其他的结构、代码、变量声明、语法跟javascript一样;只是编译器在使用了不符合类型约束的字段时会报错之类的等事情;
**自言自语:
联合类型、严格的泛型、类型推断、类型扩展(如 var test = null在非严格空检测下,可扩展为any类型、否则为null类型、null是test的为一值)、交叉类型、本地类型声明、泛型别名、用户定义的类型收窄模式(使用 is ,作为对typeof 和instanceof的补充)、类型参数的约束、不可及代码(发生在return、throw、break、continue等语句之后)、隐式返回、case语句贯穿、在模块中扩充全局或模块作用域、null和undefined类型(在严格空检测中,null和undefined不再是任何类型的有效值,而是它们自身类型或any类型的有效值)、非空断言操作符e!.name相当于if(e!=null){e.name}、抽象类的定义以及继承抽象类;--noUnusedParameters、--noUnusedLocals标记未使用的声明;关键词extends、keyof和查找类型、有条件类型、unique symbol、分布式有条件类型、有条件类型中的类型推断与关键字'infer',infer会引入一个带推断的类型以及在使用该类型时才知道具体的类型是什么、同一类型变量的多个候选类型在协变位置上(返回类型上)会被推断为联合类型,在抗变位置上(参数类型上)会被推断为交叉类型、预定义的有条件类型Exclude<T, U>,Extract<T, U>,ReturnType<T>,NonNullable<T>,InstanceType(T)、交叉类型上的keyof、识别命名空间、带元组类型的剩余参数、带有元组类型的展开表达式、类型断言(value as type, <type>value);
typescript支持与JavaScript几乎相同的数据类型,typescript还提供enum、unknown,在typescript文件中还可以自定义类型、简单的、复杂一点的,但都是由基础类型开始构建的自定义类型以及泛型语法的使用、interface、class等的使用、以及typescript的预定义类型的使用;typescript支持javascript几乎相同的声明关键字var、let、const、static;
基础类型
1,基础类型:number、string、boolean、数组(Array)、元组Tuple([])、枚举(enum Color{})、null、undefined、never、unknown、any、void;这些类型都比较容易理解;
number:数字类型,浮动数、十六进制字面量、八进制以及二进制字面量;
//一般在typescript声明变量,声明关键字如let、var、const、static(这个一般应在类class中),
//以及变量,以及变量的类型,以及赋值
//形如 var <abc>:type = value; 或用let关键字 let <abc>:type = value;定义块级变量;
//或用 const定义常量const <abc>:type = value;
//不同于 let、var这两个可以先不用赋值,const需要在定义时就赋值;
let num1: number = 6;
let num2: number = 0x15;
let num3: number = 0o7;
let num4: number = 0b10;
//在typescript的配置tsconfig.json中,属性strictNullChecks默认是false,
//所以 以上定义的变量当赋值为undefined或null时不会报错,即num1 = undefined;
//当一般不要这么做,若需要可以赋值undefined或null的话,
//使用联合类型即 let num1: number|defined | null;
//在严格空检测的情况,为类型不包含undefined的变量赋值undefined或null为报错,
//后面要讲到的类型也是如此;
***以下都假设在严格空检测的环境下;
boolean:布尔值为false/true;let bl = true;
string: 字符串类型,跟javascript一样,使用单引号、双引号,还可以是模板字符串;
//模板字符串可在字符串中间换行;
let str1: string = 'stringsss';
let str2: string = "stringsss";
let str3: string = `hello, it is a ${str2} yo`;
//变量str3 相当于 'hello, it is a stringsss yo'
undefined:undefined类型的变量只能赋值undefined或any类型的值或void函数的返回值;
//
let und: undefined = undefined;
let und = void(0);
let und = void('str');
let und = <any>2; //类型断言,将类型为number的数据转为any类型;
null: 只能给类型为null的变量赋值为为null或any类型的数据;
//
let nulVar: null;
nulVar = null;
nulVar = <any> 2; //nulVar = 2 as any也是一样的;在JSX环境下需要用as;
//一般情况下,应该不需要这样;
//这句编译成javascript的话是 nulVar = 2;
void: 表示没有任何类型;一般用在没有返回值的函数中,作为没有返回值的函数的返回类型;所以没有返回值的函数的返回值、以及undefined(毕竟没有返回值的函数默认返回undefined)、以及void函数(估计void函数就是表示没有返回值的函数的意思)的返回值;
//
let voidVar: void;
voidVar = void(0);
voidVar = undefined;
function fn(): void{
//不返回任何值;或者返回undefined;
//除了undefined可以返回之外,返回其他类型会报错;
//return undefined;
};
any: 与void相反,表示可以是任何类型;被定义为any的变量,只有在运行时根据值的类型推断才能知道具体的类型,所以在不知道具体类型时,可用any;
//any与Object的区别;
//any可以根据值推断变量此时的类型,而Object不可以;
//比如
let test: any = [2,3];
test.length; //不会报错;
let test2: Object = [2,3];
test2.length; //报错:Property 'length' does not exist on type 'Object';
test2.hasOwnProperty('test') //Okay
//any 类型的数据的使用;
let anyVar: any = 3;
let num: number = anyVar;
let anyArr: any[] = [true, 2, undefined,'str',null];
unknown:是版本3.0新加一个类型,unknown类型的值在没有类型断言或基于控制流的类型细化时不能unknown不能赋值给其他类型,除了赋值给它自己类型的和any类型的;同时unknown类型没有细化到确切类型前是不允许在其上进行任何操作的比如属性访问等;
//unknown 与 其他类型的交叉;
type t1 = unknown & string; //string;
type t2 = unknown & number; //number;
type t3 = unknown & null; // null;
type t4 = unknown & undefined & null; //never; (undefined & null is never)
type t5 = unknown & any; //any;
type t6 = unknown & string[]; //string[];
//....
//unknown 与 其他类型的联合
type t7 = unknown | string;//unknown;
type t8 = unknown | any; //any;
type t9 = unknown | string[]; //unknown;
type t10 = unknown | never;//unknown;
//这是因为几乎所有类型都能赋值给unknown;
//unknown在类型断言或用控制流细化类型前不能将unknown赋值给其他类型、
//也不可以在其上进行任何操作
function test(x: unknown): number{
let a:number;
a = unknown; //Error; 不能赋值
x.toFixed(2);//Error; 不能在其上操作;
x.hasOwnProperty('c'); //Error;
return a;
}
//使用类型断言、基于控制流的类型细化;
function test(x: unknown): number{
let a: number;
a = <number>x; //类型断言;
if(typeof x === 'number'){
x.toFixed(2); //Ok; (number).toFixed(num)返回带有num个小数位的浮点数;
}
if(typeof x === 'function'){
x.bind({}); //OK
}
return a;
}
test('string'); //OK
//估计是因为type'{}'相当于object,
//除了undefined、null、never、unknown之外的类型都可以赋给object类型;
keyof unknown; //never;
keyof any; //number, string, symbols;
type t11 = {[P in keyof unknown]: string};//等同于 type t11= {};
let obj: t11 = {}; //OK
obj1 = [2, 4]; //OK
obj1 = {hello: 'test'};//OK
obj1 = 3; //OK;
obj1 = undefined; //Error;
//需要这种形式的对象{} 或 {propperty: 'value', ...}
type t12 = {[x: string]: unknown};
let obj: t12 = {}; //OK
obj2 = {hello: 'test'};//OK
obj2 = [2, 4]; //Error; type 't12'不是数字索引签名;就是type u = {[x: number]: number}
obj2 = 3; //Error;
never:表示用不存在的值的类型,也表示程序没有可到达的终点;never是任何类型的子类型,never可以赋值给任何类型,但其他类型不能赋值给never类型;
//
function test1(): never{
throw new Error('tt'); //
}
function test2():never{
while(true){} //程序没有可到达的终点;
}
function test3():never{
return test1(); //一个返回never类型的函数 的返回值;
}
数组:Array,typescript还有ReadonlyArray(顾名思义,只读数组);
//数组Array(形如Array<type> 或 type[]):如
let arr : Array<string> = ['string']; //同
let arr: string[] = ['string'];
//如果元素的类型不确定的,可以
//let arr:any[] = [2, true, 'str', 555];
let readonlyArr : ReadonlyArray<string> = ['str', 'hello'];
readonlyArr[0] = 'test'; //Error, 因为readonlyArr的元素只读;
let ha = readonlyArr as Array<string>;
ha[0] = 'test';//OK,用类型断言将只读转为Array<string>类型;
元组Tuple(形如[type1, type2, type3, ...] ):元组是元素个数和元素的类型是确定(即使定义为any之类的,也且这么认为)的数组;
let tuples: [any] = [4, true, '444']; //Error; 元组的个数是确定的,此处是1;
let tuples1:[number, string, undefined] = [3, 'rr', undefined];//Ok
tuples1.length;//OK,
枚举enum:使用enum定义枚举类型 如 enum Color{Red, Green, Blue};或者enum Color{Red='red', 'Green'='green', Blue='clue'};;定义一个类型为枚举类型的变量let red: Color = Color.Red;或者 let green: Color=Color.Green; 枚举变量名习惯首字母大写;
//使用enum可以定义一系列带名字的常量;typescript支持基于数字或字符串的枚举;
//基于数字的枚举;枚举默认使用数字0作为第一个枚举变量的值,之后变量的值依次加1;如下:
enum EnumList {
Enum1, //相当于Enum1 = 0,
Enum2, //相当于Enum2 = 1,
Enum3 //相当于Enum3 = 2
}
//也可以在定义时手动给值;如下:当一个枚举变量的值是数字且给定后,
//之后的变量若没有指定值,会默认在前一个变量的值增加1;
enum CityNo {
Guangzhou = 1,
Shenzhen = 2,
Shanwei = 5,
Dongguan // 相当于Dongguan = 6
}
//基于字符串的枚举;
enum CityAlias {
Beijing = 'B',
Shanghai = 'H',
Tianjin = 'T'
}
//可以混用,即异构枚举
enum Test{
Fly, //相当于Fly=0,
Swim = 'Swimsuit',
Run, //没有赋值
'Dance'//相当于Dance = 'Dance',但又有些不一样;体现在编译后js代码
//Test[Test[Dance] = void 0] = 'Dance', 后者是Test['Dance'] = 'Dance'
}
//数字枚举与字符串枚举的区别
//1,基于数字的枚举,其他枚举变量可以通过访问枚举变量知道其值,
//也可以通过编号反向映射得到枚举变量;
console.log(EnumList.Enum1); //0
console.log(Enum[0]); // 'Enum1'
//2,基于字符串的枚举没有编号一说,所以不能通过编号得到其变量名;
console.log(CityAlias.Beijing); //'B',
console.log(CityAlias['B']); //undefined,没法用CityAlias['B']得到'Beijing'
没有指定类型的变量,一般其类型是any,但是在noImplicitAny的环境下,不会被指定为any类型,只有在运行才会被确定是哪种类型;比如let gh; 未被赋值前使用,会被当做是undefined类型,所以let num: number=gh会报错说type 'undefined' is not assignable to type 'number';非 noImplicitAny的环境下会是any类型,let num: number = gh不会报错,因为此时gh的类型是any;
Object、Number、String、Boolean也可以作为类型,但是一般不做这么做,对于Object会使用更具体的类型如Array、{}、class等或者object({}),对于Number、String、Boolean建议使用其对应的原始类型number、string、boolean;
let num: number = new Number(6);
//Error; type 'Number' is not assignable to type 'number'
let num1: Number = 6;//OK
//其他类似;
变量声明
2,变量声明;typescript支持javascript几乎所有的声明关键字;var、let、const、函数声明等,typescript基本支持ES6的新语法;
接口、类型、函数
3,接口、类、函数、枚举;计算属性不支持联合类型、不支持可选属性;
接口:interface,可以用来表示一个对象应满足哪些要求(约束数据结构的类型),也就是interface可以用来表示类型; interface的属性可以是可选、可以是只读、可以是用索引签名定义的属性,其属性可以是任意可用的类型,interface也可以对对象方法约束;interface还可以作为函数签名,用来作为约束函数的定义和使用,只是此时interface的定义有点不同(interface Inter{ (a: number, c: string) => void}),也就是interface既可以定义约束对象的类型,也可以定义约束函数,另外可以作为类class的接口;interface约束数据结构其实也不就是特别严谨,使用的‘鸭子辩型法’或‘结构性子类型化’;在接口中使用索引签名需要注意:索引签名参数的类型要么是'number',要么是'string';
//***不要把以下代码放一起执行,不然会发生类型合并以及若有冲突会报错;
//比如第一个interface的定义age和第二个interface中的age有冲突,因为一个必须,另一个是可选;
//一段一段地执行;
//作为对象的约束
//约束一个对象必须有age和name属性,且不能有其他属性;
//且age属性的值的类型是number,name的是string;
interface Person{
age: number,
name: string
}
let person1 : Person={age: 5,name: 'test'};//OK
let person11: Person = {name: 'test'};//Error, name在Person中有定义,
//但{age: 5}中缺少属性name赋值;
//interface对对象的约束可以不那么严格,可以使用"?"让属性称为可选属性;
interface Person{
age?: number,
name: string
}
let person2: Person = {age: 5, name: 'test'};//Ok
let person21: Person = {name: 'test'}; //OK
//interface可以选择使用字符串索引签名,这样可以允许对象有任意个属性;
//如下,aga和name是明确的属性,age可选,name是必须的,另外可以有其他任意个任意类型的属性;
interface Person{
age?: number,
name: string,
[index: string]: any
}
let person3: Person = {name: 'Test', birth: '2020-01-01', hobby: ['cry']};//OK
let person31: Person = {name: 'Test'};//OK
//所以可以使用索引签名允许一个拥有任意属性的数据结构;定义可索引类型,此时Person是个可索引类型;
interface Person{
[index: string]: any,
}
let person4: Person = {};//OK
let Person41: Person = {halo: [5, 6, 'string']};//OK
let Person42: Person = {halo: true};//Ok;因为Person中没有布尔值类型的属性;
let person43: Person = [true, 5, 'str']; //因为数组地索引也可以字符串如person43['0'];
//使用数字索引签名约束数组的数据结构;
interface Person{
[index: number]: any;
}
let person5:Person = [true, 4, 'str']; //
let person51:Person = {hello: 'test'};//Error,
//property 'hello' does not exist to type Person
let person52: Person = {}; //OK;
//interface也可以作为一个函数约束,及作为一个函数类型;
interface FnType{
(a: string, b: number):void; //调用签名;
age: number;
}
function test(){
}
test.age = 8;
let fn1: FnType = test; //OK, 签名个数可以少于定义的签名个数;参数类型逐个检查,要求类型是兼容
//返回值类型没有给,会根据返回值推断类型;
fn1('str', 6);
let val: FnType = {age: 4}; //Error; Type'{ age: number; }' provides no match
//for the signature '(a: string, b: number):void'.
//只读属性;只读属性只在赋值时可执行,但是在修改其值时会报错;
interface Person{
readonly firstName: string;
}
let person7: Person = {firstName: 'Cai'};//OK
person7.firstName = 'Li'; //Error, Property 'firstName' is readonly;
//注意****
interface Person {
readonly firstName?: string; //此时 firstName的类型会被推断为 'string | undefined'
}
//当出现同名的interface或class或其他变量时,注意类型的兼容性;如
interface Test{
[index: string]: string;
}
//和以下的定义存在不兼容;type'string' 和 type'number'不兼容;可以改为兼容的类型;
interface Test{
prop: number;
}
let test1 : Test = {prop: 67};//Error,
//property'prop' is incompatible with index signature;
//type 'number' is not assignable to type 'string';
let test2 : Test = {str: 'str'};//Error, Property'prop' is missin in type '{str:string}'
//这个定义也会出问题;因为property'prop'与index signature不兼容;
interface Test1{
prop: number; //Error, Property'prop' of type 'number' is not assignable to
//string index type 'string'
[index: string]: string;
}
以及作为类类型,此时需要注意一点,类有两部分的类型,即静态部分的类型和实例部分的类型;构造函数属于静态部分、带有static修饰符的成员也属于静态部分,像属性和方法属于实例部分,因此在这方面需要区分开,以及定义的接口是要作为类的类型还是作为类实例的类型;那么如何使用?
//可以用来指定类实例的类型的接口,其实类型检查器是利用'鸭式辩型法'
//表示该对象应有哪些成员(属性和方法)
interface SomeInstance{
say(str: string):void;
}
//以下接口的定义用来作为类的类型来约束类的定义;
interface SomeConstructor{
new(a: string, b: number):SomeInstance; //构造函数签名,构造函数返回的实例的类型
say():void; //静态方法;不同于实例的成员——方法;
}
//type 'SomeConstructor'约束类'SomeClass'的构造函数签名,
let SomeClass : SomeConstructor = class Test implements SomeInstance{
constructor(a: string, x: number){
}
say(str: string){
console.log(str);
}
static say(){
console.log('haha');
}
}
SomeClass.say();
//let inst = new SomeClass();//Error
let inst = new SomeClass('string', 67);
inst.say(); //Error
inst.say('str');
以及接口可以继承接口,接口不允许有私有(private)成员、受保护(protected)成员,修饰符'private'和'protected'不能出现在接口类型中,但接口可以继承类的私有成员和受保护成员如后边提到的'接口继承类';
interface Ts1{
a: number;
b: string;
}
//Ts2 继承了Ts1的成员;
interface Ts2 extends Ts1{
a: number;
c: number;
}
//Ts3继承了Ts2的成员;
interface Ts3 extends Ts2{
d: boolean;
}
let test: Ts3;
test = {};//Error,Type'{}' is missing the following properties from type 'Ts3':a,b,c,d
test = {a: 5, b: '655', c: 4, d: false};
test.a = 8;
test.b = '555';
以及接口继承类 和 类实现接口,接口继承类的成员但不继承其实现;在继承中、不管是私有的、公共的、受保护的成员都能被继承,但是类不能实现接口的私有的、受保护的成员,因此类要实现接口的私有成员需要通过接口和继承类继承一个拥有私有成员的基类;
//作为基类;
class Base{
private a: number;
info(str: string){
console.log(str);
}
}
interface Ts extends Base{
sayHi(): void;
}
class CL extends Base implements Ts{
sayHi(){
console.log('Hi'); //实现Ts的方法;
}
}
let test: Ts = new CL();
test.a; //Error,实例不能访问私有成员
test.info('test'); //OK, 因为Ts继承了Base
//接口'Ts1'没有继承基类;
interface Ts1{
sayHi(): void;
}
class CH extends Base implements Ts1{
sayHi(){
console.log('Hi'); //实现Ts的方法;
}
}
let test1: Ts1 = new CH();
test1.info('test');//Error,因为type 'Ts1'没有property'info';
//这就是Ts和Ts1的区别,一个继承了Base所以在接口Ts中有这个property;
//****继承了有私有成员的类的接口要作为普通对象的类型有些麻烦,
//因为私有属性即存在但又不能被访问的存在,如:
//所以继承私有成员的类的接口也只能作为一个被类实现的接口;
let test2: Ts = {info:(str: string)=> console.log(str), sayHi:()=> console.log('Hi')};
//Error;property 'a' is missing in type '{info:(str: string)=>void, sayHi:()=>void}'
let test3: Ts = {info:(str: string)=> console.log(str), sayHi:()=> console.log('Hi'), a: 7};
//Error, property 'a' is private in type 'Ts' but not in type '{......}';
//让一个类去实现有私有成员的接口会报错;
class CF implements Ts{ //Error;
//Property'a' is missing in type'CF'but required in type 'Ts';
sayHi(){
console.log('Hi');
};
info(str: string){
console.log(str);
}
}
class CF1 implements Ts{ //Error;
private a: number; //Types have separate declarations of a private property 'a'.
//不是来自Ts中的声明,它们不是同一个声明;
sayHi(){
console.log('Hi');
};
info(str: string){
console.log(str);
}
}
类: class,类class可用于作为类型约束数据结构,也可以作为构造函数创建实例对象,可实现接口也可以被当作接口被接口扩展;在类的实现体中有(只读)属性、(只读)静态属性、方法、静态方法、构造函数、各种修饰符、存取器等概念,以及抽象类的概念;
类的继承以及构造函数:类可以根据需要通过继承另一个类扩展自己;每个类都有一个构造函数,默认是没有参数签名的函数(但是会根据基类的构造函数的情况而定),构造函数名为'constructor';构造函数可以有参数列表以及指定参数的类型;
//类的继承
//静态属性以及静态方法也能被继承;
class Base{ //默认构造函数constructor(){};
a: string;
static num : number = 5;//因为后面会用,直接先赋值;
}
//派生类
class CL extends Base{ //默认构造函数constructor(){};
b: string;
}
let test: CL = new CL();
test.a;//类CL扩展了自己,继承了基类Base的成员'a';除了属性还可以是方法;
CL.num;//OK
//当基类的构造函数带有参数时;
class Base1{
a: string;
constructor(str: string){} //构造函数的参数列表以及参数的类型;
}
class CL1 extends Base1{
b: string;
}
let test1: CL1 = new CL1(); //Error, need 1 argument but got none;
//这是因为继承的基类的构造函数需传递参数,且类型是'string';
let test11: CL1 = new CL1('str');//OK;
//派生类也可以手动加上构造函数
class CL2 extends Base1{
constructor(){ //也可以根据需要指定参数列表以及参数类型;;
super('fixed');
}
}
//一个普通的类也可以根据需要手动定义构造函数
class CL3{
constructor(haha: string, reading: boolean){
}
}
let test3: CL3 = new CL3('haha', true);
let test31:CL3 = new CL3('haha', 1);//Error; type'number'is not assignable to
// type'boolean'
类的成员以及修饰符:每个类都有自己的成员,或是在类自身定义的,或是继承来;可以使用修饰符更具体定义或限制成员的行为;以及可以使用存取器(getter和setter)定义属性;
//类的成员;属性、方法、静态成员
class Test{
a: string;//类的实例属性,默认public,即可访问;
sayHi(){
console.log('Hi'); //类实例的公共方法,默认public
}
static b: string;//静态属性,可以在外部可以通过类本身访问,Test.b;
private static _name:string = 'Test'; //私有的静态属性,可以在外部可以通过类本身访问,Test.test;
static getName():string{
//return this.name;//静态方法,Test.getName(),
//那么this指向的是类Test,需要在target为ES6下可行,所以用
return this._name;
}
}
Test.b;//OK
Test._name;//Error;Property'_name' is private and only accessible within class'Test';
//修饰符的使用及其理解:
//public:表示公开的,在外部可见、派生类中可见;
//protected:表示受保护的成员,在外部不可见、在派生类中可见;
//private:表示私有成员,在外部、派生类中都不可见;
//readonly表示只读属性;readonly前还可以加上上面的3中修饰符;
class Animal{
protected age: number;//受保护的属性
}
class Fish extends Animal{
private secret: string;//私有属性
readonly waters: string;
private readonly haha: string = 'haha';
constructor(age: number, secret: string, waters: string){
super();
this.age = age; //age来自父类Animal
this.secret = secret;
this.waters = waters;
}
getAge():number{
return this.age;
}
getVoice(): string{
return this.haha;
}
}
let fish:Fish = new Fish(2, '7秒的记忆', 'Sea waters');
fish.age;//Error;
fish.secret;//Error;
fish.waters;//OK
fish.haha;//Error
//构造函数的参数属性与修饰符:
//上面的构造函数可以这样写,这样可以省略显式地写出某些属性
//但是如果看起来不简洁的话,那还是不要这样子写了;
class Fish2 extends Animal{
constructor(age: number, private secret:string, readonly waters:string,private readonly haha:string = 'haha'){
super();
}
getAge():number{
return this.age;
}
getVoice(): string{
return this.haha;
}
}
//属性存取器;注意getter和stter的定义需要在ES6环境下,设置target为ES6
class SomeClass{
private _name: string;
constructor(name: string){
this._name = name;
}
get Name(){
return this._name;
}
set Name(str: string){
// can do some filters here;
this._name = str;
}
}
抽象类:抽象一般用于派生类的继承,抽象不能实例化,抽象类中有抽象的成员(抽象属性,或抽象方法),一般抽象的成员不能有实现体,而且必须让派生类实现,但抽象类中也可以有实现体的非抽象的成员,抽象使用关键字'abstract'修饰,抽象类中也有类型的指定;
//'abstract'的修饰;首先是用'abstract'修饰class,接着用'abstract'修饰属性或方法;
abstract class Animal{
abstract age: number;
abstract name: string;
hobby: Array<string>;
sayHi():void{ //有实现体的非抽象方法;
console.log('Hi');
}
abstract sayName():void;
abstract info(str: string): void;
}
//派生类
class Fish extends Animal{
age: number;
name: string;
hobby: Array<string>; //if : hobby: ReadonlyArray<string>;
//then it would be an error, since
//the type of property'hobby' is not the same in 'Fish' and 'Animal';
constructor(age: number, name: string, hobby: Array<string>){
super();
this.age = age;
this.name = name;
this.hobby = <Array<string>>hobby;
}
sayName():void{ //实现抽象成员;
console.log(this.name);
}
// sayHi():void{ //重写抽象类的方法
// super.sayHi();
// }
info(str: string):void{
console.log(str);
}
}
let fish: Fish = new Fish(3, 'xixi', ['swim', 'play']);
函数:function,熟悉Javascript都知道函数的作用,那么在TypesScript也是一样的,只是typeScript为函数添加了类型以及类型检测,函数上的参数可以指定类型、返回值也可以指定类型,可以为一个变量指定一个函数类型,可以根据函数的类型推断函数签名的类型和返回值的类型;以及参数的可选、参数的默认值、剩余参数(剩余参数会被当做个数不定可选参数)、同名函数的重载、this参数(提供一个显式的this,this参数是个假的参数,出现在参数列表最前面)和箭头函数等知识点;
//函数签名:函数的参数、参数的类型和函数返回值类型;-------------------
function test(x: number, y: string):number{//函数签名,参数名以及其类型;
//以及返回值类型'number';那么返回值类型一定返回数字;不然报错
//若是没有返回值,可以将函数返回类型定义为'void'不用'number';
return x;//
}
//函数类型;形如'(x: number, y: string)=>number'这样的函数签名来作为函数的类型;------
//因此:如下,定义了变量'ts,它是个函数,它有如下约束:两个参数且指定了类型,指定了返回值类型;
//且将函数'test'赋值给变量'ts'
let ts:(x:number, y:string)=>number = test;
//可选参数和参数默认值;------------
//可选用'?'表示,放在参数名后;可选参数一定要放在必须参数后面;
//可选参数的类型是'undefined'和指定类型的联合;
//如下,参数y是个可选参数,它的类型是'undefined | string';而且它本身是有默认值的'undefined'
function test1(x: number, y?: string):number{
return x;
}
test1(90);
//参数默认值;
function test2(x: number, y='str'):number{ //***参数'y'的类型推断为'string'
return x;
}
test2(66);
test2(66,undefined);//OK,但不能认为参数'y'的类型是'undefined|string','y'的类型就是string
//或者需要有默认值的是参数'x'也行
function test3(x:number = 55, y:string):number{
return x;
}
test3(undefined, 'string');//55;参数'x'的类型还是'number';
//剩余参数:当不知参数个数或者希望其参数个数根据情况而定的,可以使用剩余参数;-----------
//剩余参数表示一系列这样的参数的集合;
//根据JavaScript的语法,剩余参数必须是参数列表中的最后一个参数,不然会报错,
//所以参数列表中也可以只有一个参数,而这个参数是剩余参数;
function test4(x: number, ...rest: string[]){ //此处的剩余参数是个string类型的集合
}
function test5(...rest: Array<number|string>){
}
//可以将test4赋值给这样类型的变量;
//只是这样,tsVar1函数只能接受两个参数,而不是至少1个的不限个数的参数;
let tsVar1:(x:number, y: string)=>void = test4;
tsVar1(66, '77', '88');//Error;Expected 2 arguments, but got 3;
//因此,在函数类型中也可以用剩余参数来约束参数;
let tsVar2:(x: number, ...rest:string[])=>void = test4;
//因为剩余参数可以是不限个数的参数集合,所以函数'test4'也可以赋值给以下这个类型的变量;
let tsVar3: (...rest: Array<number | string>)=> void = test4;
tsVar3('66', 77, '88');//OK;
let tsVar4:(...rest:string[])=>void = test5;
//类型推断;-----------------
//是指根据返回值的类型推断函数的返回值的类型;
function test6(){
return 55;//返回值类型是number;
}
let tsVar5:()=>void = test6;//Error,Type'()=>number' is not assignable to type'()=>void'
let tsVar6:()=>number = test6;//OK,'let tsVar6:(x: number, y: string)=>number = test6'
// is also OK;
//重载--------------
//当一个功能可能会接收不同类型的参数并返回值类型不同时,可以使用重载;
//重载就是为同一个函数提供多个函数类型定。编译器会根据根据这个列表去处理函数的调用;
//如下:函数'example'有两个重载,而'(x: any)=>any'的那个不是重载之一,
//它是重载函数的实现体,而且它是不可少的
function example(x: string):boolean;
function example(x: number[]):number;
function example(x: any):any{
if(typeof x == 'string'){
//do something, and then return a value of type 'boolean';
return true;
}
if(Array.isArray(x)){
//do something, and then return a value of type 'number';
return 55;
}
}
example('str'); //OK;
example([88]);//OK;
example({});//Error;
//this和箭头函数------------
//this参数和--noImplicitThis;当设置noImplicitThis为true时,
//编译器会指出函数中的this是any类型;因此可以使用this作为参数并放在第一个参数的位置;
//在调用的时候并不是真的要传入一个对象作为this对象,而是指定this对象的类型;
//以下代码在启用'noImplicitThis'的环境下;
let obj = {
a: ['test'],
fn: function(){
return function(){ //必须是在顶级函数中(非方法);才有这样的报错;
console.log(this.a);//Error;'this' implicitly has type 'any' because
//it does not have a type annotation.
}
}
}
//如下普通函数中使用this也会报错;
function fn(){
console.log(this.a);//Error;'this' implicitly has type 'any' because
//it does not have a type annotation.
}
//使用 this参数;---------------
type Obj = {a: string[], fn:()=>void};
let obj:Obj = {
a: ['test'],
fn: function(){
return function(this: Obj){
console.log(this.a); //OK
}
}
}
function fn(this: {a: string}){ //所以可以在某个函数参数列表中指定this的类型;
console.log(this.a);//OK
}
//或者在返回之前先绑定this的值,使用箭头函数
let obj = {
a: ['test'],
fn: function(){
return ()=> {
console.log(this.a); //OK
}
}
}
枚举:enum
泛型
4,泛型,是个强大的功能,它让类型得到更大的复用;在泛型提及到概念是所谓的泛型变量、泛型函数、泛型参数、泛型类型、泛型接口、泛型类、泛型约束等;泛型变量是指那些类型定义为泛型的变量如变量、某对象的成员、函数的参数等;泛型函数是指使用泛型类型的函数;泛型类型是指用一个变量待用户输入具体类型的变量;泛型接口和泛型类跟泛型函数类似;泛型约束是指可以使用关键字'extends'约束输入的数据结构需要满足某种结构,也就是泛型类型要满足的某种数据结构;
//泛型,泛型提高了代码的复用率;----
//如某个功能它的返回值的类型跟参数的类型有一定关系的;
//如:
// function test(x: string):string{
// return x;
// }
// //类型为'number'时
// function test(x: number): number{
// return number;
// }
//像这样的有点像重载,也可以用联合类型,但泛型更简洁:
//使用泛型的函数,即泛型函数;
//在函数后面一定要有形如'<T>'的类型参数,不然会报错:can not find name'T';
function test<T>(x: T):T{ //'x'是泛型参数、也是个变量
return x;
}
test<string>('str');//类型参数用于捕获用户输如入的类型;
test('string');//OK,也可以不传递泛型类型,它会根据输入的数据结构推断泛型的类型;
//泛型类和泛型接口;--------------
//泛型类和泛型接口都是在名字后接上形如'<T>'
//或者其他形式如'<T, U>'之类的,总之就是一对尖括号,
//里面是一个泛型类型或用逗号隔开的多个类型;
//泛型只能用于类的实例部分而不能用于静态部分;
//泛型接口还有可以是泛型函数,当为了让使用者更清楚传入的类型,定义为泛型接口也比较方便;
class GenericNumber<T>{ //泛型类型'T',类中的成员可以直接使用它;
// zeroValue: T;
constructor(public zeroValue: T){
}
getZeroValue():T{
return this.zeroValue;
}
}
let ha = new GenericNumber<string>('str');//约定类实例中的泛型类型是'string'
ha.getZeroValue();
let ha1 = new GenericNumber('str');//可以'<string>'省略;
//以下也是定义了一个泛型函数;
interface SomeFnc{
<T>(arg: T): T;
}
//也可以将泛型类型参数提到接口层面上;
interface SomeFnc1<T>{
(arg: T): T;
}
//泛型变量的使用,以及对泛型类型的约束---------------
//泛型类型不同于类型'any',可以做任意地操作,所以在使用泛型时稍微注意点;
function fn<T>(arg: T):T{
//arg.push(9);
if(typeof arg == 'string'){
return <any>(arg.replace('s', 'S')); //replace,应该是说基本字符串上的
//方法都不会改变源字符串,**给自己提个醒!!!
}
if(typeof arg == 'number'){
return <any>(arg + 3); //如果不转为'any'类型,会报错;上面的也是;
}
if(Array.isArray(arg)){//关于Array的判断,之前的代码可能使用
//Object.prototype.toString.call(arg) === '[object Array]'
//或 arg instanceof Array;
arg.push(<any>7); //
}
return arg;
}
fn([5, 6]);
//当确定一个功能要传的数据结构是个数组时;。。。,可以这样
function fn1<T>(arg: T[]):T{
return arg[0];
}
fn1([]);
//利用'extends'约束泛型类型,简单地理解就是'<T extends SomgStructure>'---
//其实是要泛型类型在结构上类似于继承'SomeStructure'
interface SomeConstraint{
a: string;
}
function fn2<T extends SomeConstraint>(arg: T):T{
console.log(arg.a); //于是此处不会报错,因为编译器知道T类型的变量有个成员'a'
return arg;
}
fn2({a: 'string', b: 66});
//***有个形如{[K in keyof SomeTypeDefinition]: SomeTypeDefinition[K]}不能写在接口中
//interface SomeConstraint{
// [K in keyof SomeConstraint]: SomeConstraint[K]; //Error
//可能跟下标签名的约束有关,本来下标签名就不能使用联合类型,如
//[index: string | number]: any;这是误的,
//下标签名参数的类型要么是'number',要么是'string'
//}
//但可以这样:
type ty = {[K in keyof SomeConstraint]: SomeConstraint[K]}
let ha3: ty = {a: 'string'};//OK
类型推断等
5,类型推断、类型兼容、联合类型、交叉类型、声明合并、定义类型(type);联合类型比较好理解、交叉类型有时候会让人混乱,主要是记混(总以为是成员间的联合);
类型推断:没有给定类型的变量或成员的初始化、有默认值的参数、没有指定返回值类型的函数的返回值;类型推断会根据数据推断出一个最为合适的通用类型;上下文类型,边界不太明确,估计就这几种情况了;
//没有指定类型的变量;-----------
let testVar = 55; //推断出testVar的类型是'number';
let testVar1: number = testVar;//OK
let testVar2=['string', 55];//testVar2的类型是Array<string|number>;
//又如
class Test{
a= 'str'; //属性a的类型是'string';
}
//有默认值的函数参数、没有指定函数返回值类型;------------
//推断参数'x'的类型是'string'
function fn(x='str'){
return x;//返回值类型是'string'
}
fn(55);//Error, 因为55是个数字,不是字符串
//又如
function fn1(){
return 77;//推断出返回值类型是'number';
}
//但是有一些不能被推断出来的类型,需要手动加上去;------------
//反向推断(上下文类型),由左边变量类型推断右边表达式的类型,如下;---------------
//右边表达式根据左边变量的了类型推断出参数'arg'的类型是'string',返回值类型是'string';
//必须参数个数是2个;但一般在写时,可省略且由左边类型推断;
let fnVar:(x: string, y: number)=>string = function fn2(arg){
console.log(arg.length); //OK
return arg;
}
fn2('str');//Error;Expected 2 arguments,but got 1;
fn2('str', 77);//OK
类型兼容:表现在一个类型转到另一个类型是否可行,若可行,则这个类型到另一个类型具有兼容性,这样就不会报错了,否则会报错;如一个'string'类型转到'string |null'类型是可行,当然反过来就不兼容了;说到类型兼容,涉及到几个概念:协变、逆(抗)变、双向协变,以及回调参数的严格抗变;协变可以简单理解为子类赋值给父类的兼容,抗变可以理解为父类赋值给子类的兼容,函数的双向协变体现在???;对抗变、协变、双向协变不太了解;Typescript结构化类型系统的基本原则:如果一个变量'x'要兼容另一个变量'y',那么'y'至少具有与'x'相同的属性;编译器会检查x中的每个属性,看是否能在y中找到对应的属性,如果都有则满足条件;且这个过程是递归比较;
//typescript的类型兼容行基于结构子类型,结构类型是一种只使用其成员描述类型的方式;----
//typescript的兼容基本原则是:-----------
//如果一个变量'x'要兼容另一个变量'y',那么'y'至少具有与'x'相同的属性;
//因此以下3个定义在结构是一样的,也与'{name: string}'一样的效果,所以它们是互相兼容的类型;
//可以用协变来概括,即 y extends x,父类兼容子类;
class Fish{
name: string;
}
class Pig{
name: string;
}
interface Test{
name: string;
}
let testVar: Fish;
testVar = new Pig();
testVar = {name: 'hello'};
let testVar1: {name: string} = new Pig();
//又如
let testVar2: {name: string, age: number} = {name: 'hello', age: 2};
testVar = testVar2;
//但是直接将一个字面赋给一个不同结构的变量会报错;----
testVar = {name: 'hello', age: 2};//Error;原因是类型不兼容;
//又如在简单的联合类型中,类型比较窄的数据可以赋值给类型比较广的变量;----
//'string' in 'string | null'中;
let str: string = 'str';
let str1: string | null = null;
// str1 = str; //OK
//而这样就不行
str = str1;//Error;因为str1有类型为'null'的用例,这样的话,null不能赋值给string;
//比较函数;-----------------
//两个函数变量是否兼容,要看必要函数参数个数、参数的类型是否兼容、返回值类型是否兼容;
//它们的比较也基于上面提到的原则;
//类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型;
//如下,先从函数个数相同的情况下,fnVar可当源函数,fnVar1可当目标函数,即fnVar=fnVar1;
function fn(arg: {a: string}): string| null{
return arg.a;
}
function fn1(arg: {a: string, b: number}): string{
console.log(arg.b);
return arg.a
}
let fnVar:(arg:{a: string})=>string|null = fn;
let fnVar1:(arg:{a: string, b:number})=>string = fn1;
fnVar = fnVar1;//OK
fnVar1 = fnVar;//Error;type'{a:string}'lacks property 'b';
//type 'string | null' can not be assignable to type'string'
//又如当函数参数个数不同但对应类型相同时,这个是参数个数少函数可以赋值给个数多的函数,
//返回值类型的兼容依然遵循上述说到的原则;
//****那么总结起来:函数参数双向协变估计就体现在这里了?, ******
//****参数个数是遵循抗变?,单个参数类型的遵循协变,返回值类型也遵循协变;
function fn2(x: string, y: number): string{
console.log(y);
return x;
}
function fn3(x: string): string{
return x;
}
let fnVar2:(x: string, y: number)=> string = fn3;
let fnVar3:(x: string)=> string = fn3;
//fnVar2 = fn1; //OK;
fnVar2 = fnVar3;//Ok;因为调用fnVar2时,执行的函数原本只支持一个参数,
//所以就算多传入一些参数导致错误,
fnVar2('str', 66);
fnVar3 = fnVar2;//Error;因为调用fnVar3时,执行的函数原本有两个参数,
//所以少传入参数可能会导致错误;
//那么考虑到函数的双向协变,总结一个例子来表示:
function fn4(x: {a: string}, y: {d: string, e: number}): string | null{
return x.a;
}
function fn5(x: {a: string, b: number}):string{
console.log(x.b);
return x.a;
}
let fnVar4 = fn4;
let fnVar5 = fn5;
fnVar4 = fnVar5;//虽然调用fnVar4,执行的函数体中有对'x.b'的使用;
//又如返回值的类型和函数返回值的指定类型, ------
//如下,也遵循协变,遵循源类型是目标类型的'子类型';
function testFn(): {a: string, b: number}{
let gh = {a: 'str', b: 77,m: 99};
return gh;
}
//带有可选参数的函数;
//对比一下上面带有不同个数参数的函数,如果多出来的是可选的参数,-----
//它是可以赋值给参数个数少的那个函数变量;
function test2(x: number, y?: string):number{
return x;
}
let ha: (x: number) => number=test2;
//留意一下带有剩余参数的函数,它就像个大海,只要类型对象,不限个参数;----
//以及可选参数与剩余参数有不一样的地方就是类型,一样的是在判断是否兼容时,
//它们可忽略,估计是TS相信在函数体内有对这类数据进行处理;
function fn6<T>(...arg: T[]){
}
function fn7(a: string, b: string){
}
let fnVar6: (a: string, b: string)=>void = fn6;
let fnVar7: <T>(...arg: T[])=>void = fn7; //Error, 因为'fn7'中有两个必要的参数
let fnVar77: <T>(a: string, b: string, ...arg: T[])=>void = fn7;//Ok
//类类型的兼容;类型类的兼容不考虑静态部分(包括构造函数),只考虑实例部分,
//其实就是比较它们的实例结构;------------
//比如
class SomeClass{
constructor(public a: string){
}
}
class SomeClass1{
a: string;
}
let clVar : SomeClass = new SomeClass('str');
let clVar1 : SomeClass1 = new SomeClass1();
clVar= clVar1;
//泛型的兼容
interface Empty<T>{ //这是个什么属性都没有的空的接口;TS又是基于结构性类型的,
//所以不管类型参数的具体类型是什么
当有属性且使用了类型参数就不一样了*****;
}
let empty1: Empty<string>={};
let empty2: Empty<number> = {};
empty1 = empty2;
//implements和extends实现的继承和受保护修饰符修饰的属性、私有属性;
//子类到父类的协变;
interface SomeIter{
a: string;
b: number;
}
class SomeClass2 implements SomeIter{
a: string;
b: number;
sayHi(){
console.log('HI');
}
}
let clVar2: SomeIter = new SomeClass2();//OK; 在结构上,也类似于'继承';
class BaseClass{
a: string;
b: string;
}
class SomeClass3 extends BaseClass{
sayHi(){
console.log('HI');
}
}
let clVar3: BaseClass = new SomeClass3();//子类赋值给父类;
//类的私有成员和受保护成员都会影响类型的兼容性;一般要求兼容的类型的这些属性来自同一类,------
//那么不同类中如果含有私有或受保护的成员,那么条件不成立即不兼容;
//但是可以让不同的类继承同一个类,这个共同类含有私有或受保护的成员时,
//那么这两个类的比较就不受影响了,如下:
class BaseClass1{
private a: string;
protected b: number;
}
class BaseClass2{
private a: string;
protected b: number;
}
class SomeClass4 extends BaseClass1{
}
class SomeClass5 extends BaseClass1{
}
class SomeClass6 extends BaseClass2{
}
let clVar4: SomeClass4 = new SomeClass5();//Ok
clVar4 = new SomeClass6();//Error,They have separate declarations of
//a private property 'a';
//enum 类型之间互不兼容,但与数字类型兼容;------------
enum Color{
Red,
Green,
}
enum City{
Red,
Green,
}
let enumVar: Color = City.Red; //Error;Type 'City' is not assignable to type 'Color'.
enumVar = 0;//OK
enumVar = 6;//OK
//但是 如果给枚举类型的属性取值为字符串的话,编译会报错;如
enum Color1{
Red = "Red"
}
let enumVar1:Color1 = 0;//'Error'
let enumVar1:Color1 = 'Red';//'Error'
联合类型:是指使用字符'|'竖杠将多个类型联合起来的组成一个新的类型,这个类型就是联合类型;如简单的 'string | number',则表示类型支持'string' 或者'number'的数据;官方的描述是:联合类型是指一个值可以是几种类型之一。用竖线(|)分割每个类型,所以string | number | boolean表示一个值可以是number、string、boolean;***注意TS是基于结构化类型的,一个值不可能同时是两种结构化类型,就是不可能同时是number和string;
//官方例子
class Bird{
fly():void{
}
layEggs():void{
}
}
class Fish{
swim():void{
}
layEggs():void{
}
}
//加了点东西;
function getPet(arg: number): Fish | Bird{
//
if(arg === 1){
return new Fish();
}
return new Bird();
}
//函数里返回的是Fish的实例,按理说,应该可以拿到属于它的成员swim,但是却报错,
//这是因为联合类型只能确保几个类型之间的共同成员是安全,不能保证其他成员,
//因此只能访问几个类型的共同成员,而访问其他不管是那个类型的成员(非共同的)会报错;
//这也是这个例子要表达的注意点和用法;
//因此当需要用到非共同成员,以及知道返回值类型时可以做类型断言;
let pet = getPet(1);
pet.layEggs();//OK;
pet.swim();//Error;
let pet1 = <Fish>getPet(1);
pet1.swim();//OK;
交叉类型:稍微复杂一些,但也可以理解,交叉类型中的多个类型的相同属性名的类型要求是一样的,就连可选不可选也保持一致,不然会报错;它有点像是extend,把不同类型的成员联合起来组成一个可能比较广的对象,这个对象包含所有成员的定义,如interface Ha{a: number} & interface He{a: number; b: string}交叉成{a: number, b: string};可能是把不同类型交叉成一个看似不相干的类型如 ‘string&number’交叉之后是类型'never';相同成员的声明(类型或者约束)要求一致,不然冲突;使用字符'&'作为几个类型的连接;官方的定义是:交叉类型是将多个类型合并为一个类型,如 Person&Loggable(这两个表示两个类型如某个class或某个interface之类的),它要求这个类型的对同时拥有这两种类型的成员;因此没有交集的'string'与'number'的交叉类型是'never';****'string & number' 与'String & Number'不一样;
//文档有个例子
function extend<T, U>(first: T, second: U):T&U{
let result = <T&U>{}; //定义一个变量,类型为'T&U';
for(let p in first){
result[p] = <any>first[p]; //这是将first[p]的值将为'any'类型;
//因为result跟 first还有second的类型不兼容;
}
for(let p in second){
result[p] = (<any>second)[p]; //这是将second转为'any'类型;
}
return result;
}
class Person{
constructor(public name: string){ //定义了'name'属性
}
}
interface Loggable{
log():void
}
class ConsoleLoger implements Loggable{//实现接口的'log'方法
log(){
}
}
var jim = extend(new Person('Jim'), new ConsoleLoger());
jim.name;
jim.log();
类型保护:利用某些方法以确保对不太具体的数据做了正确的操作;官方描述:类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型;类型保护的方式有几种:1,函数的返回值是个类型谓词'parameterName is Type',并用该函数判断参数的类型是否是指定那个指定类型(Type);2,typeof类型保护,用typeof操作符直接判断一个变量的类型,但是需要注意的是typeof varivable的到的结果是字符串,是('string' | 'number' | 'symbol' | 'boolean' | 'object' | 'undefined' | 'function')之一(目前来说);3,instanceof类型保护,是通过构造函数来细化类型的一种方式;instanceof的右边要求是个构造函数,TS将细化为构造函数的prototype的类型如果它不为any的话或者细化为构造签名所返回的类型的联合(前者优先考虑); 顺带把这几个概念也在这提一提:可为null的类型、可选参数和可选属性、类型保护和类型断言、去掉null和undefined(if语句、短路运算符、添加'!'后缀);
//
更多高级类型:
类型定义(类型别名):使用关键字'type'定义类型变量 如type ty = string | null; 那么ty是个类型引用,允许的数据是'string'类型的或'null'类型的;
//官方例子
type Name = string;//为类型'string'定义一个别名;
type NameResolver = () => string;//为一个函数签名定义一个别名;
type NameOrResolver = Name | NameResolver; //为联合类型起一个别名;
//或者直接 type NameOrResolver = string | (()=> string);
function getName(n: NameOrResolver):Name{
if(typeof n === 'string'){
return n;
}
else{
return n();
}
}
//为一个类似树结构的结构类型定义一个别名;---
type Tree<T> = {
value: string;
left: Tree<T>;
light: Tree<T>;
}
//官方说与交叉类型一起使用,定义类型别名引用一些稀奇古怪的类型??;--
//为一个类似指针链的结构类型起一个别名;
//运用到交叉类型'&'
type LinkedList<T> = T & {next: LinkedList<T>|null}; //加上可为null,是希望它有个终点;
interface Person{
name: string;
}
var person:LinkedList<Person> = {name: 'hello', next: {name: 'test', next: null}};
person.name;
person.next!.name;//
//***官方说会报错,但在本机测试时,没有报错!?---
//**但不管怎么说,这句以及上边的两个例子确实有点怪,怪就怪在没有限制的话,就会一直循环;
//就像是 Array<Array<Array<.....>>>,简直就像是魔鬼;因此一定要小心,避免这类的类型引用;
type Yikes = Array<Yikes>;
//一个类似接口的并带有泛型的类型别名;
type Container<T> = {value: T};
字符串字面量类型:字符串字面量类型指的是将一个字符串作为类型引用;如let hello: 'hello' = 'hello';值和类型必须一样;字符串字面量类型可以赋值给字符串类型'string';
//字符串字面类型,指的是将一个字符串作为类型引用;---
//如
let hello: 'hello' = 'hello';//OK
let hi: 'hello' = hello;//OK
hello = 'kaka';//Error; Type "'kaka'" is not assignable to type 'hello';
//编译器也把字符串'kaka'的类型推断为字符串字面量类型'kaka',
//并将其与类型'hello'比较;
hello = 6; //Error;Type '6'(数字字面量类型) is not assignable to type 'hello';
//将数字6推断为数字字面量类型;
//赋值给字符串类型的变量-----
let str: string = hello;//OK
//可以将联合多个字符串字面量类型;---
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';
function animate(dx:number, dy: number, ease: Easing){
let test : 'ease-in' = ease; //Error;
let test1: 'ease-in' = <'ease-in'>ease;//OK,这只是举个例子,因为ease不一定是'ease-in';
if(ease === 'ease-in'){
//....
}
if(ease === 'ease-out'){
//....
}
//......
}
数字字面量类型:数字字面量类型是指将数字作为类型用;如let num: 1= 1; 值必须和类型一样;数字字面量类型可以赋值给数字类型'number';
//数字字面量类型;
let test: 1 = 1;
test = 2; //Error; Type'2' is not assignale to type '1';
//数字字面量类型的联合;
type Nums = 0 | 1 | 2 | 3;
let num: Nums = 0;
//其他的用法跟其他类型类似;
let a: number = test;
可辨识联合的高级模式:使用可辨识联合要求满足三要素(也可以说它有3个要素):1,具有普通的单例类型属性——以此属性为可辨识的特征。2,一个类型别名包含了那些类型的联合——联合;3此属性上的类型保护;以及可辨识联合模式在函数式编程中的用处;
//官方例子
//首先是单例类型如下,且每个单例类型都有个可辨识的特征,在这个例子里,这个特征是属性'kind'
//使用类型别名将这几个单例类型联合;
//使用switch语句做类型保护的工作;
//最后写一个函数以及default语句做完整性检查,以此来提醒用户有遗漏的case;
//单例类型以及可辨识特征;可辨识特征为这几个类型的相同属性名;
interface Square{
kind: 'Square';
size: number;
}
interface Rectangle{
kind: 'Rectangle';
width: number;
height: number;
}
interface Circle{
kind: 'Circle';
radius: number;
}
//这几个类型的联合 并定义一个别名;或许这就是所谓的可辨识联合;
type Shape = Square | Rectangle | Circle;
//使用可辨识联合
function area(s: Shape){
switch(s.kind){//可以直接访问kind属性,switch case 控制流可起到类型保护,
//类似于if-else语句
case 'Square': return s.size * s.size;
case 'Rectangle': return s.width * s.height;
case 'Circle': return Math.PI * s.radius**2;
default: return assertNever(s); //完整性检查;
}
}
//完整性检查;当遗漏某些case的时候,可做提醒;
function assertNever(x:never): never{
throw new Error('Unexpected object: ' + x);
}
多态的this类型:this可以作为类型;多态的this类型表示的是包含(?)类或接口的子类型(所以this代表的是子类型的意思!?);它能很容易的表现连贯接口间的继承,比如对象的方法中返回this;?因此也可以说多态的this类型是个动态的类型,在运行时会根据子类型的不同,它引用的实例也不同;
//官方例子
//使用了多态的this类型的基类,它是个具有不确定形态的类型,
//在运行时可确定;
class BasicCalculator{
constructor(protected value: number=0){}
getCurrentValue():number{
return this.value;
}
add(oprand:number):this{
this.value = this.value + oprand;
return this;
}
multiply(oprand: number):this{
this.value = this.value * oprand;
return this;//返回实例对象,默认类型是'this'
}
}
//当使用自身构造函数创建一个实例时,this类型引用的对象的类型是BasicCalculator;
new BasicCalculator().add(5).multiply(3);
//在接口中,也能使用多态的this类型;
interface Test{
test():this;
}
//继承基类'BasicCalculator'还实现了接口'Test'的子类型;
//此时的多态this类型指向的是子类型;
class ScienttificCalculator extends BasicCalculator implements Test{
constructor(value=0){
super(value);
}
sin(){ //返回值类型是this;
this.value = Math.sin(this.value);
return this;
}
test():this{
return this;
}
}
let val= new ScienttificCalculator(5)
.add(6)
.multiply(5)
.sin()
.test()
.getCurrentValue();//这种链式调用的形式估计就是连贯性;
//假设不使用多态的this类型
class Basic{
add(oprand: number):Basic{ //它返回构造函数类型;
return this;
}
}
class Child extends Basic{
multiply(oprand: number):this{
return this;
}
}
new Child()
.add(4)
.multiply(6);
//Error;因为上一步的执行返回的类型是'Basic',在'Basic'没有'multiply'这个方法
//比对之后可以看出多态的this带来的便利以及意义;
索引类型、索引类型查询操作符、索引访问操作符、索引签名:索引类型是指将索引作为类型,那它将会是个联合类型,如interface Person{name: string; age: number}; 那么这个接口类型的索引类型是" 'name' | 'age' ";编译器可以使用所以类型检查使用了动态属性名的代码;索引类型查询操作符是指'keyof' 操作符,它的使用是keyof Type,然后得到索引的联合如keyof Person得到是'name | age';索引访问操作符是指使用'[ ]'将索引作为属性下标来访问该索引在类型对象中对应的值(是个类型)如Person['name'],得到的是'string';索引签名类似于函数签名,包括索引变量、索引变量的类型、索引的值(索引签名的类型),如interface Test{[key: string]: number},那么'key'就是索引变量、类型'string'是索引变量的类型、类型'number'是索引的值;字符串索引签名是指索引变量的类型是字符串'string',数字索引签名指的是指索引变量的类型是数字'number';
//泛型函数,'K extends keyof T' K继承自T的键;----------
//且编译器会检查K是否在T的keys中;
//类型'K[]'是个动态类型的数组,'T[K][]'也是个动态类型的数组;
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][]{
return names.map(n=>o[n]); //o[n]会是数组中一员
}
interface Person{
name: string;
age: number;
}
let person: Person = {
name: 'Hello',
age: 5
};
let strings: string[] = pluck(person, ['name']);
//pluck<Person, 'name'>(person, ['name']);
//在上面的例子中,pluck函数的调用,T是Person,K是'name',
//K[]就是'name'[],看到可以联想到字符串字面量,'name'就是个字符串字面量类型;
//T[K]是string,所以T[K][]是指string[];
//当索引类型、索引访问操作符 和字符串索引签名的交互时------
//keyof T 如keyof Test<number>, 拿到的是'string'
//T[string], 拿到的是'number';
interface Test<T>{
[index: string]: T;
}
let test: keyof Test<number>;//string;貌似是索引的类型
let test1: Test<number>['foo'] //number; 索引签名的类型
映射类型:从旧类型中创建新类型的一种方式;
//
interface Person{
name: string;
age: number;
}
//通过泛型以及类型映射 生成一个新的类型
//加上readonly
type Readonlys<T> = {
readonly[k in keyof T]: T[k]; //这种语法内部使用了for...in...语法,具有3个部分
//1,k是类型变量,
//2,keyof T拿到的是字符串字面量联合,也是属性名的集合
//3,T[k]是属性的结果类型;
}
//加上可选符号;
type Partials<T> = {
[k in keyof T]?:T[k];
}
//注意点:
//映射类型只能放在类型的位置上;
//而不是生成一个接口;如
interface Partialss<T>{
[k in keyof T]?: T[k]; //error;
}
//也可以直接这样,像这样不用泛型的,但它只能固定为Person类型的映射,
//泛型的复用性比较高;
type Test = {[k in keyof Person]?: Person[k]}
有条件类型:表示根据条件的判断,返回符合条件的类型;有条件类型可以嵌套条件(形如T extends U? U: T extends U[]? U[]: T);一般形式的有条件类型(T extends U? X: Y)、分布式有条件类型(什么叫分布式?如果条件类型中待检查的类型是naked type parameter(裸漏的类型参数),那么它也被称为‘分布式有条件类型’)、有条件类型中的类型推断(哪种形式?在extends子句中允许出现关键字'infer'用于表示待推断类型如 infer U,U是个待推断类型)、预定义的有条件类型(TS增加了几种可能会常用的预定义有条件类型)、改进对映射类型修饰符分控制(如何控制?通过符号'+'、'-','+'表示添加某些修饰符如+readonly或+?;'-'表示去掉某些修饰符如-readonly或-?)、改进交叉类型上的keyof(如何改进的?用在交叉类型上的keyof如 type = keyof({a: string} & {b: string})则相当于keyof({a:string}) | keyof({b: string})的联合);
// 形如 T extends U ? X: Y;------------------------
//一般形式;例子;
type TypeName<T> = T extends string? 'string' :
T extends number? 'number' :
T extends boolean? 'boolean':
T extends undefined? 'undefined':
T extends Function? 'function':
'object';
//当类型参数T是以下某一种的时候;
type T0 = TypeName<string>; //得到的类型是 string
type T1 = TypeName<'a'>;//string,因为字符串字面量类型是 'string'的子类型
type T2 = TypeName<true>; //boolean;
type T3 = TypeName<()=>void>;//function
type T4 = TypeName<string[]>;//object
//分布式有条件类型;分布式有条件类型在实例化时会自动分发成联合类型;------------------------
//如下面定义的 type Boxed<T>,以及在调用时,Boxed<string | number[]>自动分发成联合类型;
//因此,Boxed<string | number[]>相当于Boxed<string>|Boxed<number[]>;
type BoxedValue<T> = { value: T };
type BoxedArray<T> = { array: T[] };
type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>; //待检查的类型参数是T;
//T[number]中的number表示下标的类型,
//T[number]则表示数组元素的类型;
type T20 = Boxed<string>; // BoxedValue<string>;
type T21 = Boxed<number[]>; // BoxedArray<number>;
type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;
//分布式有条件类型可以用于过滤联合类型------------------------------
type Diff<T, U> = T extends U ? never : T;//去掉是与U的子类型的类型;
type Filter<T, U> = T extends U ? T: never;//去掉不是U的子类型的类型;
type T30 = Diff<'a' | 'b' | 'c' | 'd', "a" | "c" | "f">;// 'b' | 'd';
type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">;//'a' | 'c'
//因为 NonNullable是TS的一个预定义的有条件类型,所以换成'NonNullableTest';
type NonNullableTest<T> = Diff<T, null | undefined>;//将类型T 过滤成NonNullable的类型;
function f1<T>(x: T, y: NonNullableTest<T>) {//此时y是NonNullbale的数据;
x = y; // Ok
y = x; // Error
}
function f2<T extends string | undefined>(x: T, y: NonNullableTest<T>) {
x = y; // Ok
y = x; // Error
let s1: string = x; // Error
let s2: string = y; // Ok
}
//有条件类型与映射类型的结合;--------------------------------
type FunctionPropertyNames<T> = {[K in keyof T]: T[K] extends Function? K: never}[keyof T];
//***上边这个例子的用于:右边的首先是'T[K] extends Function? K: never'找出类型是函数的属性,
//那么'{[K in keyof T]: T[K] extends Function? K: never}'得到的
//暂时当成{K: kx | never}(当K不是函数类型时取never),那么
//{K: kx | never}[keyof T]拿的是'{K: kx | never}'中的所有属性值的类型的联合即
//'kx | never'即'kx',注意这里的取名kx是为了与'K'区分开,具体可以以下例子代入分析;
//估计 Pick的实现类似是这样的: Pick是TS预定义的类型;所以用Pick1代替;
type Pick1<T, U extends string | symbol | number> = {[K in U]: K extends keyof T ? T[K]: never};
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Part{
id: number;
name: string;
subParts: Part[];
updatePart(newName: string): void;
}
//那么
type T40 = FunctionPropertyNames<Part>;//'updatePart'
type T41 = NonFunctionPropertyNames<Part>;// 'id' | 'name' | 'subParts'
type T42 = FunctionProperties<Part>; //{updatePart(newName: string): void}
type T43 = NonFunctionProperties<Part>;//{id: number, name: string, subParts: Part[]}
//有条件类型中的类型推断---------------
//利用'infer'使用待推断类型比较方便,若不用带推断类型,估计需要多定义一些类型参数;
//比如,
type test<T, U> = T extends ()=> U ? U: T;
//若使用'infer'的话; 'infer'关键字 的功能挺强大的,看官方例子;***
type test1<T> = T extends()=> infer U? U: T;
type testRetn = test<()=> string, string>;
type test1Retn = test1<()=> string>;
//官方例子 ----------------
//关键字'infer'可以出现的地方只要是在extends子句中的任意的类型位置上,且要求是有条件的类型中即要有条件,否则不支持;
//'infer'指定待推断的类型在有条件类型在实例化时被确定推断;*****
type UnPacked<T> = T extends (infer U)[]? U:
T extends (...args: any[])=> infer U? U:
T extends Promise<infer U>? U:
T;
//
type T50 = UnPacked<string>;//string;是怎么得到'string'的? 首先是上面的条件都不符合,
//最后返回自己T;
type T51 = UnPacked<string[]>;//string; 符合第一个, infer U推断出来的U是'string'
type T52 = UnPacked<()=>string>;//string; 符合第二个条件
type T53 = UnPacked<Promise<string>[]>;//Promise<string>;符合第一个条件
type T54 = UnPacked<Promise<string>>;//string; 第三个条件;
//在协变的位置上,同一类型变量的多个候选类型会被推断为联合类型----------
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T70 = Foo<{ a: string, b: string }>; // string
type T71 = Foo<{ a: string, b: number }>; // string | number
// type Test1<T> = T extends {a: infer U, b: infer U}? U: T;
// type T60 = Test1<{a: string, b: string}>;//string
// type T61 = Test1<{a: string, b: number}>;//string | number;
//在抗变的位置上,同一类型变量的多个候选类型会被推断为交叉类型-------
type Bar<T> = T extends {a: (x: infer U)=>void, b: (x: infer U)=> void}?U: never;
type T72 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string
type T73 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
// type Test2<T> = T extends (a: infer U, b: infer U)=>void? U: T;
// type T62 = Test2<(a: string, b: string)=> void>;//string
// let variable0: T62 = 'fhfhfh';
// type T63 = Test2<(a: string, b: number)=> void>;//string & number,即never;
// let variable1: T63= (function():never{throw new Error('');})();
//当推断具有多个调用签名的类型时,使用最后一个签名进行推断--------------
//如下,推断出来的类型是'string | number',因为返回该类型的签名的位置在后面,
//所以当返回'boolean'的签名放到最后时,这时推断出来的类型是'boolean';
declare function foo(x: boolean): number;
declare function foo(x: number): boolean;
declare function foo(x: string | number): string | number;
type T74 = ReturnType<typeof foo>; // string | number
let fg: T74 = true;//Error;type 'boolean' is not assignable to type 'string |number';
声明合并:当声明的名字相同的情况下,这种合并有点类似于类型的交叉;可能是接口与接口,也可能接口与类,也可能是类与类(不可以),函数与函数(函数重载),也可以命名空间与命名空间,也可能命名空间与其中之一;合并也有注意事项,有些声明不能合并,有些声明的成员类型有冲突,那么这些声明不能合并成功且编译器会报错,那么这时候就要根据需要做出修改了;官方描述:‘声明合并’是指编译器将针对同一名字的两个独立声明合并为单一声明,合并后的声明同时拥有原先两个声明的特性;任何数量的声明都可被合并不局限于两个。
//命名空间与其他类型进行合并时,命名空间需要放在其他声明的后面;----
//测试的时候,一个单元一个单元测,不然会报错,不能合并的会报重复声明的错------
//命名空间与命名空间的合并----
//命名空间里,只有export出来的API才能其他声明合并,
//其他的namespace里的非导出声明都是只在该namespace中可见,即使是namespace同名也不可见;
namespace NS{
export interface Inter{
a: string;
}
export class CL{
c: string;
}
const hello = 'hello';
}
namespace NS{
export interface Inter{
a: string;
b: number;
}
var hi = 'hi';
export function fn(){
console.log(hello); //Error;
}
}
let val0: NS.Inter = {a: 'str', b: 78};//OK
let val1: NS.CL = {c: 'string'};//OK
// //命名空间与函数的合并; --------------
// //拿上边第一个命名空间的定义;
function NS(arg: string){
console.log('hi');
}
//命名空间的定义需要放在这***
//命名空间的属性都会过给函数,相当于是命名空间扩展了函数
NS('str');
new NS.CL();
命名空间与interface的合并;-------
相当于是扩展了interface;
interface NS{
a: NS.Inter;
}
//命名空间定义放这**
let val0: NS = {a: {a: "hh"}};
//命名空间与enum类型的合并;-------
//扩展enum
enum NS{
Red
}
//命名空间定义放这**
let val2 : NS.Red = 5;
new NS.CL();
//命名空间与class的合并;--------
//合并是个类,命名空间导出的API相当于该类的静态属性;
class NS{
a: NS.Inter;
}
//命名空间定义放这**
let val1: NS = {a: {a: 'str'}};
//interface与interface的合并;--------
//interface与interface或class的合并;
interface Test{
a: string;
}
class Test{
b: number;
}
let val2: Test={a: 'str', b: 33};
new Test().a;
//函数与函数的合并,只能是函数重载;-----
function logInfo(str: string): number;
function logInfo(num: number): string;
function logInfo(...args: any[]):any{
return '';
}
TS中的声明会创建以下三种实体之一: 命名空间(Namespace)、类型(Type)和值(Value);如下图所示(归类图):列出的声明类型可以归类为那三种实体(有的声明可以创建不同的实体),打'X'的表示该类型可归类到此实体中;如namespace表示可以作为namespace声明(declare namespace Test{export var test: string;}),也可以作为value声明(namespace Test{export var test: string = 'Value';});
模块&命名空间
6,模块、命名空间;
模块:模块是自声明的,意思就是一个文件就是一个模块;两个模块间的关系是通过在文件级别上使用imports和exports建立的,其实就是首先一个模块通过import的形式之一取获取另一个模块的接口的时候,编译器会检查这另一个模块是否是个模块,怎么检查,通过检测另一个模块中在顶层是否有export或import的行为,否则,编译器会报错说这不是一个模块,不能用import的形式会访问这个文件;因此这个文件里顶层的定义被当作是全局的定义,可以通过'script'标签引入该文件编译后的js文件、或者通过引用标签在ts文件中先声明它们之间的关系如reference path='...' 就可以调用这个文件定义的api了,记得这一定是个自闭标签,就是后面的斜杆一定要写上,不然会无效;一个模块可以导出声明(包括变量、接口、类、类型别名、函数),通过关键字export完成导出如 export var test="string",每个模块都可以有且只有一个默认导出如 var test ="string";export default test;也可以重新导出一个已有模块 export * from 'anotherModule'; 可以通过import关键字导入模块如 import * as Test from 'TestModule';模块内的声明除了export出去的都是只在模块内可见,一旦一个文件确定是个模块后,不能用引用标签引用(因为无效,模块内的定义仍是不可见);
//定义一个模块;该模块定义一些逻辑或着接口;
//使用官方文档的例子;
//模块是自声明的,一个有export或import的文件就是一个模块;
//如下有4个模块;
//validation.ts;
export interface StringValidator{ //声明了一个接口并导出;可以 在 eport后加上default
//像这样子, export default interface StringValidator{...}
isAcceptable(s: string): boolean;
}
//ZipCodeValidator.ts(另一个模块)
import {StringValidator} from './validation';
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator { //当只导出一个声明的时候,
//可以在export后加上default;当然可视情况而定
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
//LettersOnlyValidator.ts(另一个模块)
import {StringValidator} from './validation';//当上述模块使用了default,那么
//这里可以 import StringValidator from './validation'
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator{//当只导出一个声明的时候,
//可以在export后加上default,
isAcceptable(str: string):boolean{
return lettersRegexp.test(str);
}
}
//allValidator.ts; 重新导出已有模块
export * from './LettersOnlyValidator';//若该模块使用了export default,
//那么这里需要这样;export {default as LettersOnlyValidator} from '...'
//下同;
export * from './ZipCodeValidator ';
//test.ts;
import * as validators from './allValidator';
new validators.LettersOnlyValidator();
//anotherModule.ts
//可以export 字面量数据之类
export default 123;
//index.ts
import num from 'anotherModule';
使用module关键字为外部模块定义类型;
//假设有个模块为 someModule.js
//someModule.js
var test = function(){
console.log('test');
};
var obj = {
val: 'hello',
sayHi: function(){ console.log('hi') }
inter: interface inter{
a: string;
sayHi(): void;
}
};
//test.ts
//当没有为模块KL声明文件时,会有如下问题(在 --noImplicitAny 为true时)
import * as KL from 'KL';
KL.test(); //Error; Counl not find declaration file for module 'KL',
//'...KL.js' implicitly has an 'any' type;
//以下也会有问题;
class ho extends KL.obj.inter{
a: string = '55';
sayHi(){
return this;
}
test(){
console.log('haha');
}
}
new ho().sayHi().test();
//这个时候有两种选择可以解决这个问题:
//1,显式地声明模块'kl'为any类型;可以说是最基本的;
//2,为这个模块写声明类型;
//选择 1时候,
//declaration.d.ts
declare module 'KL';//就这一句就OK,另外在test.ts文件中,在import这个模块地上面加上一句:
// ///<reference path="declaration.d.ts" />
//选择 2时,为'KL'写声明文件;
//modules.d.ts
//declare module 'KL'; //也需要加上 ///<refrence path="modules.d.ts">
declare module 'KL'{ //与被导入模块保持模块名一致;
interface Obj{
val: string;
sayHi(): void;
inter: new()=>this;
}
export var test: ()=>void;
export var obj: Obj;
}
//另外在一个文件可以有多个模块类型声明,如在'modules.d.ts'中还
//可以有 declare module 'otherModule';
命名空间:可以解决命名冲突(当声明过多时,可以归类,把一些放在namespace中),也可以扩展某个文件(当代码单个文件多大时,可以分离代码成几个文件);在模块中不要为了命名冲突的问题使用module而是使用namespace解决;
//例子
//在某个文件使用namespace给自身使用
//test.ts
namespace Validation{
export interface StringValidator{
isAcceptable(str: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LetterOnlyValidator implements StringValidator{
isAcceptable(str: string):boolean{
return lettersRegexp.test(str);
}
}
export class ZipCodeValidator implements StringValidator{
isAcceptable(str: string):boolean{
return numberRegexp.test(str);
}
}
}
let validator: Validation.StringValidator = new Validation.LettersOnlyValidator();
//命名空间内的变量必须export才能使用,且需要是命名空间使用属性访问来访问export出来的API;
validator.isAcceptable('abcd');
//把命名空间单独放在一个文件中,貌似一个文件只能有一个namespace(如果是在声明文件中估计可以多个)-
//Validation.ts
namespace Validation{
export interface StringValidator{
isAcceptable(str: string): boolean;
}
}
//LettersOnlyValidator.ts
///<refrence path="Validation.ts" /> //引用文件;以使用已有的API;
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
//ZipCodeValidator.ts
///<refrence path="Validation.ts" />
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
//test.ts
///<reference path="Validation.ts" />
///<reference path="LettersOnlyValidator.ts" />
///<reference path="ZipCodeValidator.ts" />
let validator: Validation.StringValidator = new Validation.LettersOnlyValidator();
//以上三个文件中的命名空间取同一个名字,使用起来会比较方便;
//若是使用不同的命名,假如在ZipCodeValidator.ts中命名是AnotherValidation,那么这个文件在
//使用'Validation'export出来的API时,需要带上命名空间'Validation'如
//Validation.StringValidator
//namespace的使用算是比较灵活的;
//另外,namespace和declare namespace不同,declare namespace中不能有实现体,如
//export var test: string = 'str'会报错,因为在 declare namespace中变量不能赋值、
//方法不能有实现体,可以认为declare namespace是声明外部脚本库的API的类型的;
//如:
//D3.d.ts
declare namespace D3{//声明
export interface Selectors{
select: { //这个属性的值的类型是个函数,而且是个重载函数,参数类型不同;
(selector: string): Selection;
(element: EventTarget): Selection;
}
}
export interface Event{
x: number;
y: number;
}
export interface Base extends Selectors{
event: Event;
}
}
declare var d3: D3.Base;//声明一个变量,d3一般是个全局变量,它通过<script>引入;
//test.ts
///<reference path="D3.d.ts" /> //引用类型声明文件;
d3.select('strtrt');
d3.select(<Element>document.querySelector('test'));
使用module和namespace为外部库(js 文件)写声明文件,即为外部库声明所需要的而类型;一个是为了模块(通过模块机制引入的文件)、一个是为了脚本库(通过标签script引入的文件);但不管是为了内部声明还是外部声明,module和namespace的用法很相似;用作为声明文件的module和namespace在其他模块中的调用方式都是用引用标签reference;
引用标签reference:path与types,types指向的是个目录,会寻找该目录下的index.d.ts文件;
import和export:import有几种形式:import * as all from '...'; 或 import someD from '...'; 或着有import '...';或者定义一个别名import myModule from '...'; import test = myModule.test(用import 为myModule.test定义一个别名,同时也是该别名创建一个新的引用,即改变test的数据,不会影响 myModule.test);或者import all = require('...')(用在模块目标即--module是CommonJS、AMD的情况下);export有几种形式:export var str: string = 'tyttt'; export default 'tyttt'; 或者 export = [varibale](export = 某个对象,这个对象可以是函数、字符串、数字、类等等;用在--module为CommonJs、AMD);
参考文档