typescript建立在ES6语法上
安装和编译
- 安装typescript包
- 指令:npm install -g typescript
- 将ts文件编译为js文件
- 指令:tsc 对应文件名
Ⅰ基本数据类型
- boolean
- number 和js一样,所有数字都是浮点数
- string
- 数组
- number[]
- Array
- any
- void
- enum 枚举类型
enum Color {Red = 1, Green, Blue};
var c: Color = Color.Green;//2
//事实上此时对应的js为:
//Color[Color["Green"] = 2] = "Green";
//即Color.Green为2,且Color[2]为'Green'
默认起始数值为0,依次递增,也可以自己设置(且仅能为数字)
- 元组:允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let x: [string, number];
x = ['hello', 10];
当访问一个越界的元素,会使用联合类型替代
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
undefined和null
两者各自有自己的类型分别叫做undefined和null。 和void相似,它们的本身的类型用处不是很大:
默认情况下null和undefined是所有类型的子类型。 就是说你可以把null和undefined赋值给number类型的变量。
类型断言(对于类:用于你清楚地知道一个实体具有比它现有类型更确切的类型。对于接口:强制假设某值符合该接口而绕开编译器的检查)
let a:any = '666';
let aLength = (a as string).length;
或
let aLength = (a).length;
Ⅱ接口
对象接口
TypeScript的核心原则之一是对值所具有的shape进行类型检查,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
一个不使用接口的例子:
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
此时只要参数为含有label为string的对象即可,而不限制是否有其他属性
改为接口后:
interface LabelledValue {
label: string;
name?: string;//可选参数
readonly age: number;//只读参数,仅能在对象刚创建时赋值,之后不能修改
todo?():void;//方法属性
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
printLabel( {size: 10, label: "Size 10 Object",age: 18} );会报错
因为对象有额外的属性检查:不允许出现接口中不存在的属性
解决方法:
1 索引签名
添加一个为any的可索引类型
[propName: string]: any;
2 类型断言
printLabel( {size: 10, label: "Size 10 Object",age: 18} as LabelledValue);
或
printLabel( {size: 10, label: "Size 10 Object",age: 18});
3 重新赋值
let myObj = {size: 10, label: "Size 10 Object",age: 18};
printLabel(myObj);
函数接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
if (result == -1) {
return false;
}else {
return true;
}
}
console.log(mySearch('hello','h'));
函数的参数名不需要与接口里定义的名字相匹配
可索引类型
共有支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。
可索引类型会限制所有其他属性必须和其一致
interface NumberDictionary {
readonly [index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型不是索引类型的子类型
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
类接口(采用implements实现一个接口)
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
接口继承类
扩展接口
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {//一个接口可以继承多个
sideLength: number;
}
let square = {};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;//必须同时满足两个接口
注意TS中不能像js一样,var obj={};然后obj.color='blue';
但是可以借助类型断言进行类似的操作
Ⅲ类
声明一个类
class Hero {
id: number;
name: string;
}
用类构造实例
var lala1:Hero;//空对象
lala1={
id:20,
name:'hh'
};
var lala2:Hero={//具体赋值
id:20,
name:'hh'
};
var HEROES: Hero[] = [//构造一个对象的数组
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' }
];
注意,以下几种写法是错误的
var lala1:Hero;
lala1.id=10;//此时lala1为不存在
var lala1:Hero;
lala1={//不能有不符合Hero类的属性,也不能有遗漏或多余的属性
id:20
};
使用继承扩展一个类
class Animal{
name:string;
age:number;
constructor(theName:string='my name'){
this.name=theName;
}
move(distance:number=100){
console.log(this.name + 'move' + distance)
}
show(){
console.log('this is show')
}
}
let cat=new Animal('puppy');
cat.move(666)
class Dog extends Animal{
name:string;//子类只能让基类的属性更具体,而不能违反;不声明时即和基类相同
weight:number;
constructor(ddd){
super(ddd);//constructor内super必须在this之前
this.weight=20;
}
move(){//子类方法可以覆盖基类
console.log(this.name+this.weight);
super.show()
}
}
let dog=new Dog('ddd');
dog.move()
包含constructor函数的派生类必须调用super(),它会执行基类的构造方法。
公有,私有与受保护的修饰符(public,pravate,protected,readonly)
在TypeScript里,每个成员默认为public的。
一旦成员被标记成private,它就不能在声明它的类的外部通过对象.属性的方式访问。但是可以通过class内public的方法将该成员返出。
两个类即使有同名private成员也无法兼容,除非该private成员有同一来源
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // Error: Animal and Employee are not compatible
protected类似于private,区别在于可以在派生类中访问。
构造函数也可以被标记成protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee can extend Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected
此外你可以使用readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
参数属性
通过给构造函数参数添加一个访问限定符来声明,可以方便地让我们在一个地方定义并初始化一个成员。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
//简化为:
class Animal {
constructor(private name: string) { }
move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
静态属性 static
类的实例成员,仅当类被实例化的时候才会被初始化。在类中用 this. 访问。
类的静态成员,存在于类本身上面而不是类的实例上。在类中用 类. 访问。
class Animal {
name:string;
static age: number;
move(){
this.name='hello2';
Animal.age=20;
return this.name+Animal.age;
}
}
var dog = new Animal();
console.log(dog.move());
console.log(dog.name);
console.log(Animal.age);
抽象类与抽象方法 abstract
抽象类是供其它类继承的基类。 他们不能直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。
abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象方法不包含具体实现并且必须在派生类中实现。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
把类当做接口使用
你可以在需要使用接口的时候用类代替。例如用一个类实现另一个类,或者一个接口继承一个类。
Ⅳ 函数
在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。
一个完整的函数:
let myAdd: (baseValue:number, increment:number) => number =
function(x: number, y: number): number { return x + y; };
参数
TypeScript里的每个函数参数都是必须的。传递给一个函数的参数个数必须与函数期望的参数个数一致,不管形参比实参多还是少,都会导致报错
可通过在形参后加 ? 实现可选参数功能,且可选参数必须写在必须参数的后面,如下 :
function fn(a:number,b?:any){ }
fn(100)
或直接赋一个默认值,如下 :
function fn(a:number,b='hello'){ }
fn(100)
不同于前者,赋予默认值的参数不一定要写在后面,但是如果写在了前面,必须实参给予一个undefined,才会采用默认值,如下 :
function fn(a='hello',b){ }
fn(undefined,100)
函数中同ES6语法,可以用剩余参数:
function(a,b,...c: string[]){ }
- 函数重载:
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
pickCard方法根据传入参数的不同会返回两种不同的类型。它查找重载列表,依次尝试,如果成功则采用并中止重载。 因此定义重载时,要把最精确的定义放在最前面。
注意,function pickCard(x): any并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接收数字。 以其它参数调用pickCard会产生错误。
Ⅴ泛型
虽然使用any类型后这个函数已经能接收任何类型的arg参数,但是却丢失了一些信息:传入的类型与返回的类型应该是相同的。 如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。
function move(age:A):A[]{//通过A[]保证泛型具有length属性,或通过泛型约束
return [age,age];
}
console.log(move(18).length);
两种泛型函数接口的写法
interface GenericIdentityFn {
(arg: T): T;
}
function identity(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
interface GenericIdentityFn {
(arg: T): T;
}
function identity(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
泛型类
class GenericNumber {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}