typescript学习
1.TypeScript 基础类型
在TypeScript与JavaScript中几乎有相同的数据类型,不同的是TypeScript更严格,在定义一个变量时需要声明数据类型。如果不声明,都认定为 Any 类型。
a.布尔值
最基本的数据类型就是简单的true/false值
let isDone: boolean = false;
b. 数字
TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number
let decLiteral: number = 6; let hexLiteral: number = 0xf00d; let binaryLiteral: number = 0b1010; let octalLiteral: number = 0o744;
c.字符串
字符串用法较多,常用有两种。
1.定义变量 let name:string = "momo"; 2.字符串模版 let name1:string = "wang"; let age:number = 24; let sences:string = `hello ,my name is ${name}.i am ${age} years old`
d.数组
TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上 []
,表示由此类型元素组成的一个数组:
let list:number[] = [1,2,3]
第二种方式是使用数组泛型,Array<元素类型>
:
let list:Array<number> = [1,2,3];
e.元组
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let x:[string,number]; x = ['hello', 10]; // ok x = [10,'hello']; // error
f.枚举
enum
类型是对JavaScript标准数据类型的一个补充。使用枚举类型可以为一组数值赋予友好的名字。
enum Color {Red,Green ,Blue} let c:Color = Color.Green; console.log(c); // 0
默认情况下,从0
开始为元素编号。 你也可以手动的指定成员的数值.
enum Color {Red = 1,Green ,Blue} let c:Color = Color.Green; console.log(c); // 2
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。
例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1,Green ,Blue} let colorNumber:string = Color[2]; console.log(colorNumber); // Green
g.Any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。
这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。
这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any
类型来标记这些变量:
let notSure:any = 4; notSure = 'momo'; notSure = false;
h.void
某种程度上来说,void
类型像是与any
类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void.
声明一个void
类型的变量没有什么大用,因为你只能为它赋予undefined
和null
:
let unusable:void = undefined;
2.变量声明
let
和const
是JavaScript里相对较新的变量声明方式。
let
在很多方面与var
是相似的,但是可以帮助大家避免在JavaScript里常见一些问题。
const
是对let
的一个增强,它能阻止对一个变量再次赋值。
let 和 const在ES6中有详细解释说明,这里就不多阐述了。
3.数组
数组解构
let input = [1, 2]; let first = input[0], second = input[1]; console.log(first);
这创建了2个命名变量 first
和 second
。 相当于使用了索引,但更为方便
first = input[0];
second = input[1];
你可以在数组里使用...
语法创建剩余变量:
let [first, ...rest] = [1, 2, 3, 4]; console.log(first); // 1 console.log(rest); // [ 2, 3, 4 ]
对象解构
let o = { a: "foo", b: 12, c: "bar" }; let { a, b } = o;
这通过 o.a
and o.b
创建了 a
和 b
。 注意,如果你不需要 c
你可以忽略它。
就像数组解构,你可以用没有声明的赋值:
({ a, b } = { a: "baz", b: 101 });
要小心使用解构。 从前面的例子可以看出,就算是最简单的解构表达式也是难以理解的。
尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。
解构表达式要尽量保持小而简单。 你自己也可以直接使用解构将会生成的赋值表达式。
4.接口
TypeScript的核心原则之一是对值所具有的结构进行类型检查。
它有时被称做“鸭式辨型法”或“结构性子类型化”。
在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
接口初探
function text(lableObj) { console.log(lableObj.lable); } var myObj = { size: 10, lable: "size 10" }; text(myObj); //size 10
类型检查器会查看text的调用。 text有一个参数,并要求这个对象参数有一个名为lable
类型为string
的属性。
需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。
然而,有些时候TypeScript却并不会这么宽松,我们下面会稍做讲解。
下面我们重写上面的例子,这次使用接口来描述:必须包含一个lable
属性且类型为string
:
interface lableValue{ lable:string; } function text(lableObj:lableValue){ console.log(lableObj.lable); } let myObj = {size:10,lable:"size 10"} text(myObj); //size 10
lableValue
接口就好比一个名字,用来描述上面例子里的要求。
它代表了有一个 lable
属性且类型为string
的对象。
需要注意的是,我们在这里并不能像在其它语言里一样,说传给text的对象实现了这个接口。
我们只会去关注值的外形。 只要传入的对象满足上面提到的必要条件,那么它就是被允许的。
还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
interface SquareConfig{ color?: string; width?: number; } function creatSquare(config:SquareConfig):{color:string,area:number}{ let newSquare = {color:"white",area:0}; if(config.color){ newSquare.color = config.color; } if(config.width){ newSquare.area = config.width; } return newSquare; } let mySquare = creatSquare({color:"black"}) console.log(mySquare); //{ color: 'black', area: 0 }
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
值得一提的是,被圈上的部分是函数creatSquare的返回值,如果函数没有返回值,应该标记为:void。
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly
来指定只读属性:
interface Point{
readonly x:number;
readonly y:number;
}
如果传入参数时,传入了接口中没有的参数,比如:
interface SquareConfig{ color?: string; width?: number; } function creatSquare(config:SquareConfig):{color:string,area:number}{ let newSquare = {color:"white",area:0}; if(config.color){ newSquare.color = config.color; } if(config.width){ newSquare.area = config.width; } return newSquare; } let mySquare = creatSquare({colour:"black"})
这个时候会得到一个错误,// error: 'colour' not expected in type 'SquareConfig'
解决办法:
最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果 SquareConfig
带有上面定义的类型的color
和width
属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig { color?: string; width?: number; [propName: string]: any; }
我们稍后会讲到索引签名,但在这我们要表示的是SquareConfig
可以有任意数量的属性,并且只要它们不是color
和width
,那么就无所谓它们的类型是什么。
函数类型
除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。
它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc{ (source:string,subString:string):boolean; }
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。
下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
interface SearchFunc{ (source:string,subString:string):boolean; } let mySearch:SearchFunc; mySearch = function(source:string,subString:string){ let result = source.search(subString); return result > -1; }
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 比如,我们使用下面的代码重写上面的例子:
interface SeachFunc{ (source:string,subString:string):boolean; } let mySeach:SeachFunc; mySeach = function(src:string,sub:string):boolean{ let result = src.search(sub); return result > -1; }
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。
如果你不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc
类型变量。
函数的返回值类型是通过其返回值推断出来的(此例是 false
和true
)。
如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与 SearchFunc
接口中的定义不匹配。
可索引的类型
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]
或ageMap["daniel"]
。
可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray{ [index:number]:string; } let myArray:StringArray; myArray = ["bob","fred"]; let myStr = myArray[0]; console.log(myStr); //bob
共有支持两种索引签名:字符串和数字。
可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
这是因为当使用 number
来索引时,JavaScript会将它转换成string
然后再去索引对象。
也就是说用 100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,因此两者需要保持一致。
class Animal { name: string; } class Dog extends Animal { breed: string; } // 错误:使用数值型的字符串索引,有时会得到完全不同的Animal! interface NotOkay { [x: number]: Animal; [x: string]: Dog; }
你可以将索引签名设置为只读,这样就防止了给索引赋值:
interface ReadonlyStringArray { readonly [index: number]: string; } let myArray: ReadonlyStringArray = ["Alice", "Bob"]; myArray[2] = "Mallory"; // error!
总结:
1.interface SeachFunc {}; 相当于定义了类型,并且只能按照这个类型传值;
2.let mySeach:SeachFunc; 相当于具体了内容,进行怎么样的操作;
3.let myObj = mySeach("wang","w"); 相当于实例化;
5.类
下面看一个使用类的例子:
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet():string { return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); console.log(greeter.greet()); //Hello, world
我们声明一个 Greeter
类。这个类有3个成员:一个叫做 greeting
的属性,一个构造函数和一个 greet
方法。
你会注意到,我们在引用任何一个类成员的时候都用了 this
。 它表示我们访问的是类的成员.
最后一行,我们使用 new
构造了 Greeter
类的一个实例。 它会调用之前定义的构造函数,创建一个Greeter
类型的新对象,并执行构造函数初始化它。
继承
在TypeScript里,我们可以使用常用的面向对象模式。
基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
class Animal{ move(distanceInMeters:number = 0){ console.log(`Animal moved ${distanceInMeters}`); } } class Dog extends Animal{ bark(){ console.log("woof!woof!"); } } const dog = new Dog(); dog.bark(); //woof!woof! dog.move(10);//Animal moved 10 dog.bark();//woof!woof!
这个例子展示了最基本的继承:类从基类中继承了属性和方法。
这里, Dog
是一个 派生类,它派生自Animal
基类,通过 extends
关键字。 派生类通常被称作 子类,基类通常被称作 超类。
因为 Dog
继承了 Animal
的功能,因此我们可以创建一个 Dog
的实例,它能够 bark()
和move()
。
class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal{ constructor(name: string) { super(name); } move(distanceInMeters = 45){ console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom:Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
这个例子展示了一些上面没有提到的特性。
这一次,我们使用 extends
关键字创建了 Animal
的两个子类: Horse
和 Snake
。
与前一个例子的不同点是,子类包含了一个构造函数,它 必须调用 super()
,它会执行基类的构造函数。
而且,在构造函数里访问 this
的属性之前,我们 一定要调用 super()
。 这个是TypeScript强制执行的一条重要规则。
这个例子演示了如何在子类里可以重写父类的方法。
Snake
类和 Horse
类都创建了 move
方法,它们重写了从 Animal
继承来的 move
方法,使得 move
方法根据不同的类而具有不同的功能。
注意,即使 tom
被声明为 Animal
类型,但因为它的值是 Horse
,调用 tom.move(34)
时,它会调用Horse
里重写的方法