typescipt手册
基础
静态类型检查
TypeScript的静态类型检查器会利用类型系统提供的信息,在有类型错误时及时报错提醒,不同于JS的弱类型,在运行时计算值的类型。尽可能的避免了在大型系统中不能及时排查出一些潜在的错误而导致进行大规模的代码重构,或者是发现不了一些潜在的错误。例如:
const message = "hello";
message();
JavaScript执行后会抛出错误:This expression is not callable. Type ‘String’ has no call signatures.
TypeScript会在执行前抛出一个类型错误,并提供提示信息:This expression is not callable. Type ‘String’ has no call signatures.
非异常失败
上述讨论的错误抛出为运行时错误,是因为ECMAScript规范明确规定了针对异常应该表现的行为。Javascript在有些情况下不能及时捕获到某些错误,此时需要Typescipt来解决这个问题。例如:
javascript在访问不存在的属性时不会抛出错误,而是返回一个undefined
const user = {
name: 'Daniel',
age: 26,
};
user.location; // 返回 undefined
同样是上述代码,在Typescript中,会抛出一个错误,指出location未定义:Property ‘location’ does not exist on type ‘{ name: string; age: number; }’.
因此可以看出,Typescript可以找出程序中更多合法的bug,也能捕获到更多合法的bug
类型工具
Typescript可以在代码出现错误时捕获bug,也可以在一开始就防止代码出现错误。
类型检查器可以通过获取的信息检查我们是否正在访问变量或者其它属性上的正确属性。
一旦它获取到了这些信息,它也能够提示你可能想要访问的属性。
这意味着 TypeScript 也能用于编辑代码。我们在编辑器中输入的时候,核心的类型检查器能够提供报错信息和代码补全。
TypeScript编译器——tsc
npm安装:
npm install -g typescript
执行ts:
tsc xxx.ts
执行之后会产出一个js文件,内容为标准的js代码。
如果ts中有存在错误,执行后不影响产生文件。产生的文件中会擦除ts文件中显式声明的类型,同时从高版本的ECMAScript重写为低版本的。
严格性
在tsconfig.json
中的strict: true
配置项,可以一次性开启全部严格性设置。严格性设置可以快速的尽可能多的检查代码。
基础类型
布尔值
最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean
(其它语言中也一样)。
数字
与Javascript一样,TypeScript里的所有数字都是浮点数,类型为number。
除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
字符串
使用string表示文本数据类型。
let name: string = "lihao"
数组
TypeScript和JavaScript一样可以操作数组元素。
定义方式:
在元素类型后接[]
:
let list: number[] = [1,2,3];
使用数组泛型Array<元素类型>
:
let list: Array<number> = [1,2,3]
元组Tuple
元组类型允许表示一个已知元素数量和类型的数组,例如:
const array: [string,number];
array = ['nihao',6];
当访问一个越界元素是,会使用联合类型替代:
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString
枚举
enum
枚举类型类似于其他语言的枚举,可以为一组数值赋予名字。
//默认情况下从0开始编号
enum Color {Red, Green, Blue}
let c: Color = Color.Green;//c = 1;
//可以手动指定成员的数值
//指定从1开始编号
enum Color {Red=1,Green,Blue};
//全部手动赋值
enum Api{
login = '/login',
register = 'register'
}
同时枚举类型可以由枚举值查找到名字,例如:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
alert(colorName); // 显示'Green'因为上面代码里它的值是2
任意值any
当在编程阶段不清楚类型的变量时,例如一些动态的内容和用户输入内容等,此时可以使用any
类型来标记
let notSure:any = 4;
空值void
void
表示没有任何类型,一般用于没有返回值的函数
function fun():void{}
void
声明的变量只能赋值为undefined
和null
,因此没有什么用。
null和undefined
默认情况下null
和undefined
是所偶有类型的子类型,因此可以赋值给任何类型的变量(不能赋值给never)。
never
never
表示永不存在的值的类型,例如那些总是会抛出异常或根本不会有返回值的函数表达式或箭头函数表达式的返回值类型。
never
类型是任何类型的子类型,也可以赋值给任何类型
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
类型断言
当你清楚地知道一个实体具有比它现有类型更确切的类型时,可以使用类型断言。它没有运行时影响,只在编译阶段起作用。
写法一:尖括号
let someValue:any = "string";
let strLength:number = (<string>someValue).length
写法二:as
let someValue:any = "string";
let strLength:number = (someValue as string).length
接口
当调用函数时传入参数为一个对象且知道对象的属性类型时,例如在调用接口api时往里传入参数时,此时的参数类型后端文档已经明确告知,在这种情况下可以考虑使用接口定义,例如:
interface loginForm{
userName:string;
password:string;
}
function login(data: loginForm){
....
}
//调用函数
let loginForm = {userName:'lili',password:'123',id:1};
login(loginForm);
上述代码指定了loginForm
中必须包含userName
和password
属性且类型为string
;
可选属性
使用?
将属性标记为可选属性:
interface config{
data?:string;
method?:string;
}
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。例如属性名拼写错误等。
只读属性
当对象属性只能在对象刚刚创建时修改值,后续不能修改只能读时,可以在属性名前用readonly
来指定只读属性:
interface Point{
readonly x:number;
readonly y:number;
}
TypeScript具有ReadonlyArray<T>
类型,它与Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
let a: number[] = [1,2,3,4];
let ro: ReadonlyArray<number> = a;
//此时ro只能被读取不能被修改
最简单判断该用readonly
还是const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const
,若做为属性则使用readonly
。
额外的属性检查
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
let mySquare = createSquare({ colour: "red", width: 100 });
上述代码传入的参数是colour
而不是color
,在JavaScript中会默默地失败。
但是在TypeScript
中会认为这段代码存在bug,对象字面量会被特殊对待而且会经过额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });
可以使用类型断言绕开这些检查:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
也可以增加一个字符串索引签名,当能够确定这个对象可能具有某些做为特殊用途使用的额外属性:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
函数类型
接口除了描述带有属性的普通对象外,也可以描述函数类型。
使用接口表示函数类型,需要给接口定义一个调用签名。就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface func{
(source:string,subString:string): boolean;
}
//创建一个函数变量
let myFunc: func;
myFunc = function(source:string,subString:string){
return 1> -1
}
可索引的类型
接口也可以描述能够通过索引得到的类型。可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型
interface StringArray{
[index:number]:string;
}
let myArray: StringArray;
myArray = ["Bob","Fred"];
let myStr: string = myArray[0];
这个索引签名表示了当用number
去索引StringArray
时会得到string
类型的返回值。
共有支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number
来索引时,JavaScript会将它转换成string
然后再去索引对象。 也就是说用100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,因此两者需要保持一致。
类类型
interface
类似于java和c#里的接口,可以明确的强制一个类去符合某种契约
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
setTime(d: Date) {
this.currentTime = d;
}
getTime(){
return this.currentTime;
}
}
继承接口
接口可以互相继承,可以从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape{
color: stringl;
}
interface Square extends Shape{
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
混合类型
接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。
一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter{
(start: number): string;
interval: number;
reset():void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
接口继承类
当接口继承了一个类类型时,会继承类的成员但不包括其实现。接口声明了所有类中存在的成员,但没提供具体实现。接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
class Control{
private state:any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
}
// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
select() { }
}
class Location {
}
类
与其他面向对象语言用法类似,用关键词class
声明:
class Dog{
name:string;
constructor(name: string){
this.name = name
}
wang(){
return 'wangwangwang';
}
}
let dog = new Dog("lili");
继承
面向对象模式是允许使用继承来扩展现有的类。例如:
class animal{
name:string;
age:number;
}
class Dog extends animal{
bark(){
console.log('wang wang');
}
}
const dog = new Dog();
dog.bark;
dog.name = 'lili';
dog.age = 1;
派生类包含了一个构造函数,它必须调用super()
,它会执行基类的构造函数。 而且,在构造函数里访问this
的属性之前,我们一定要调用super()
。 这个是TypeScript强制执行的一条重要规则。例如:
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);
范围修饰符
public
默认情况下成员为public修饰,即可以被自由的访问。
private
用private
声明的成员不能在声明类以外访问,此时的访问权限为私有的。
protected
protected
修饰的成员在派生类中可以被访问,即在声明类的子类中被访问。
构造函数也可以被标记成protected
。 这意味着这个类不能在包含它的类外被实例化,但是能被继承
readonly
readonly
关键字声明的属性为只读的。只读属性必须在声明时或构造函数里被初始化。
静态属性
static
声明的变量属性是一个静态属性,即类的静态成员。这些属性存在于类本身而不存在于类的实例上。例如:
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
抽象类中的抽象方法不包含具体实现,并且必须在派生类中实现。
函数
和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。 你可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。
函数类型
给每个函数的参数以及函数返回值指定类型:
function add(x: numuber,y: number): number{
return x+y;
}
let myAdd = function(x: number, y: number):number{return x+y}
可选参数和默认参数
JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 在TypeScript里我们可以在参数名旁使用?
实现可选参数的功能。
unction buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
可选参数必须跟在必须参数之后。
在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined
时。 它们叫做有默认初始化值的参数。 让我们修改上例,把last name的默认值设置为"Smith"
。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right
剩余参数
在TypeScript里,你可以把所有参数收集到一个变量里:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
泛型
在像C#和Java这样的语言中,可以使用泛型
来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件
例如,当传入的参数类型不确定时,可以使用any来修饰参数类型,但是如此就失去了typescript的意义,因此可以使用泛型来解决。
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function func<T>(arg: T): T{
return arg;
}
用上述方式定义的函数称为泛型函数,调用方式:
let out = func<string>("string");
//利用类型推断器来进行推断
let out = func("string");
泛型类
直接把泛型类型放在类后面,可以帮助我们确定类的所有属性都使用相同的类型。泛型接口的用法也类似
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };