搭建环境
使用npm,全局安装TypeScript
npm install -g typescript
// 手动编译
tsc xxx.ts
// vscode 自动编译
1). 生成配置文件tsconfig.json
tsc --init
2). 修改tsconfig.json配置
"outDir": "./js",
"strict": false,
3). 启动监视任务:
终端 -> 运行任务 -> 监视tsconfig.json
基础类型
TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了枚举等类型。
字符串
用 string表示文本数据类型。和JavaScript一样,可以使用双引号( ")或单引号(')表示字符串。
let uname = 'Ynag';
uname = 'PH';
支持模版字符串
,它可以定义多行文本和内嵌表达式。
let uname = 'Ynag';
uname = 'PH';
let info = `我叫${uname}`;
数字
和JavaScript一样,TypeScript里的所有数字都是浮点数
。 这些浮点数的类型是 number
。 除了支持十进制
和十六进制
字面量,TypeScript还支持ECMAScript 2015中引入的二进制
和八进制
字面量。
let num1: number = 6;
let num2: number = 0xf00d; // 十六进制
let num3: number = 0b1010; // 二进制
let num4: number = 0o744; // 八进制
布尔值
简单的true/false值.
let isFlag: boolean = false;
数组
有两种方式可以定义数组。
第一种,可以在元素类型后面接上 []
,表示由此类型元素组成的一个数组:
let numArr:number[] = [1, 2, 3];
let strArr:string[] = ['张三', '李四', '王五'];
第二种方式是使用数组泛型,Array<元素类型>
:
let numArr: Array<number> = [1, 2, 3];
let booleArr: Array<boolean> = [true, false];
元组 Tuple
元组类型允许表示一个已知元素数量
和类型
的数组,各元素的类型不必相同。
let tuple: [string, number, boolean, string];
tuple = ['张三', 20, true, '李四']; √
tuple = ['张三', 20, false, '李四', 200]; ×
当访问一个已知索引的元素,会得到正确的类型:
console.log(tuple[0].substr(1)); // OK,字符串
console.log(tuple[1].substr(1)); // Error, 类型“number”上不存在属性“substr”
当访问一个不存在的元素,在2.7版本以后不被允许:
tuple[4] = 'world'; // 不能将类型"world"分配给类型“undefined”
枚举
enum
类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字。
enum Gender {
Man,
Woman
}
let gender1: Gender = Gender.Man;
let gender2: Gender = Gender.Woman;
console.log(gender1, gender2); // 0, 1
默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值。
enum Gender {
Man = 3,
Woman = 6
}
let gender1: Gender = Gender.Man;
let gender2: Gender = Gender.Woman;
console.log(gender1, gender2); // 3, 6
你也可以由枚举的值得到它的名字。
let gender3: string = Gender[6];
console.log(gender3); // Woman
Any
为不清楚类型的变量指定一个类型,这些值可能来自于动态的内容,我们不希望类型检查器对这些值进行检查。
let val: any;
val = '张三';
val = 10;
val = true;
let arr:any[];
arr = ['张三', 10, true];
arr[3] = '李四';
console.log(arr); // ['张三', 10, true, '李四']
Void
void类型像是与any类型相反,它表示没有任何类型。
let str:void = 10; // Error, 不能将类型“number”分配给类型“void”
let str1:void = null; // Error, 不能将类型“null”分配给类型“void”
let str2:void = undefined; // OK
当一个函数没有返回值时,通常其返回值类型是void。
function logMsg(): void {
console.log('hello world!');
}
logMsg();
Null 和 Undefined
TypeScript里,undefined
和null
两者各自有自己的类型分别叫做undefined
和null
。
let u: undefined = undefined;
let n: null = null;
默认情况下null
和undefined
是所有类型的子类型。 就是说你可以把 null
和undefined
赋值给
一切。
但在严格类型模式中,null
和undefined
值不再属于任何类型的值,仅仅属于它们自己类型和any
类型的值(例外:undefined
也能赋值给void
)。
// Error
let u1: undefined = null;
let n1: null = undefined;
let u2:string = undefined;
let n2:number = null;
// OK~
let u3: string | undefined = undefined;
let n3: number | null = null;
let u4: boolean | null | undefined;
u4 = true;
u4 = null;
u4 = undefined;
在严格空检查模式中,编译器要求未包含undefined
类型的局部变量在使用之前必须先赋值。
let x: number;
let y: number | null;
let z: number | undefined;
x; // 错误,使用前未赋值
y; // 错误,使用前未赋值
z; // 正确
x = 1;
y = null;
x; // 正确
y; // 正确
Never
never类型表示的是那些永不存在的值的类型。抛出异常的函数,不会有返回值的表达式,箭头函数表达式的返回值类型。
// 函数返回never必须无法执行到终点
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
}
}
// 推断返回类型是never
function fail() {
return error("Something failed");
}
“never”仅表示类型,不能作为值使用。
Object
object
表示非基本类型的类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。
let obj:object = {
name: '张三',
age: 18
}
obj = {
name: '李四',
age: 20
}
declare function create(o: object | null): void;
create({
name: '张三'
})
类型断言
了解某个值的详细信息,比它现有类型更确切的类型。原始any
类型,告诉编译器是string
类型。类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
另一个为as
语法:(建议√)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
只是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。
接口
对值所具有的结构进行类型检查.
interface Person {
name: string,
age: number,
job: string
}
let person: Person = {
name: '张三',
age: 20,
job: '程序猿'
}
function logPerson(person: Person):void {
console.log(`我叫${person.name}, 今年${person.age}了, 我的工作是${person.job}`);
}
logPerson(person);
可选属性,接口里的属性不全都是必需的。可以对可能存在的属性进行预定义,可以捕获引用了不存在的属性时的错误。
interface SquareConfig {
color?: string;
width?: number;
}
interface Square {
color: string,
area: number
}
function createSquare(config: SquareConfig): Square {
let newSquare: Square = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
只读属性, 一些对象属性只能在对象刚刚创建的时候修改其值
interface Point {
readonly x: number;
readonly y: number;
}
let p: Point = { x: 10, y: 20 };
p.x = 15; // Error
TypeScript具有ReadonlyArray<T>
类型,它与Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
let arr:number[] = [1, 2, 3, 4];
arr.push(5);
console.log(arr); // [1, 2, 3, 4, 5]
let ra:ReadonlyArray<number> = [9, 8, 7, 6];
console.log(ra); // [9, 8, 7, 6]
ra[0] = 10; // Error
ra.push(5); // Error
ra.length = 10; // Error
ra = arr; // OK
arr = ra; // Error, 把整个ReadonlyArray赋值到一个普通数组也是不可以的
把整个ReadonlyArray
赋值到一个普通数组也是不可以的,但是你可以用类型断言重写。
a = ro as number[];
readonly
vs const
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。
做为变量使用的话用 const,若做为属性则使用readonly。
额外的属性检查
对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig):void {
// ......
}
// Error, 对象文字只能指定已知的属性,“colour”中不存在类型“SquareConfig”
let mySquare = createSquare({ colour: "red", width: 100 });
解决方案,使用类型断言:
let mySquare = createSquare({ opacity: 0.5, width: 100} as SquareConfig);
最佳方案,添加一个字符串索引签名,表示有任意数量的属性(不是width、color)。
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
不建议使用,跳过这些检查的方式,将这个对象赋值给一个另一个变量。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig):void {
// ......
}
let squareOptions = {
colour: "red",
width: 100
};
// squareOptions不会经过额外属性检查,所以编译器不会报错
let mySquare = createSquare(squareOptions);
函数类型
像是一个只有参数列表
和返回值类型
的函数定义。参数列表里的每个参数都需要名字和类型。
// 正常函数书写格式
function fun(first: number, last: number): boolean {
return first > last;
}
// 接口描述的函数类型
interface Compare {
(first: number, last: number): boolean;
}
let size: Compare = function (first: number, last: number): boolean {
return first > last;
}
// 精简写法
let myCompare: Compare = function(a, b) {
return a > b;
}
console.log(size(10, 20));
console.log(myCompare(20, 10));
类类型
TypeScript也能够用类类型来明确的强制一个类去符合某种契约。
interface ClockTime {
currentTime: Date,
setTime?(d: Date):void
}
class Clock implements ClockTime {
currentTime: Date
constructor(h:number, m:number) {
console.log(h, m);
}
setTime(d:Date) {
console.log(d);
}
}
let d = new Date();
let clock = new Clock(d.getHours(), d.getMinutes());
clock.setTime(d);
继承接口
从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface Animal {
name: string,
age: number
}
interface Cat extends Animal {
color: string,
[attr:string]: any // 添加一个字符串索引签名
}
let cat = {} as Cat;
cat.name = '七月';
cat.age = 2;
cat.color = 'orange';
cat.breed = '橘猫'; // 类型“Cat”上不存在属性“breed”
console.log(cat);
一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {
color: string
}
interface Circle {
width: number
}
interface Square extends Shape, Circle {
border: number
}
let square = {} as Square;
square.color = 'red';
square.width = 10;
square.border = 2;
类
类的基本使用
class Cat {
catName: string
constructor(catName: string) {
this.catName = catName;
}
say ():void {
console.log(`大家好,我的名字叫${this.catName}.`);
}
}
let cat = new Cat('七月');
cat.say();
类的继承
class Animal {
uname: string
constructor (uname: string) {
this.uname = uname;
}
logName ():void {
console.log('大家好,我是' + this.uname);
}
}
class Dog extends Animal {
breed: string
uname: string
constructor (uname: string, breed: string) {
super(uname); // 派生类的构造函数必须包含 "super" 调用
this.breed = breed;
}
breeds ():void {
console.log('我的品种是' + this.breed);
}
}
let dog = new Dog('七月的风', '哈巴狗');
dog.logName();
dog.breeds();
更复杂的例子,多重继承
class Animal {
uname: string
constructor (uname: string) {
this.uname = uname;
}
move (distance: number) {
console.log(`${this.uname}走动了${distance}m.`);
}
}
class Dog extends Animal {
uname: string
constructor (uname: string) {
super(uname); // 派生类的构造函数必须包含 "super" 调用
}
move (distance: number) {
console.log(`我是犬科物种......`);
super.move(distance);
}
}
class Cat extends Animal {
uname: string
constructor (uname: string) {
super(uname); // 派生类的构造函数必须包含 "super" 调用
}
move (distance: number) {
console.log(`我是猫科物种...`);
super.move(distance);
}
}
let dog = new Dog('七月');
dog.move(100);
let cat = new Cat('八月');
cat.move(190);
公共,私有与受保护的修饰符
成员都默认为 public
class Animal {
public uname: string
public constructor (uname: string) {
this.uname = uname;
}
public move (distance: number) {
console.log(`${this.uname}走动了${distance}m.`);
}
}
当成员被标记成 private
时,它就不能在声明它的类的外部访问。
class Animal {
private uname: string
constructor (uname: string) {
this.uname = uname;
}
}
let animal = new Animal('猫');
animal.name; // Error, ‘uname’是私有的
protected
成员在派生类中仍然可以访问。实例化后,不允许被使用。
readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
函数的使用
函数的基本使用
function max(x:number, y:number):number {
return x + y;
}
max(10); // Error, 应该有2个参数
max(10, 20); // OK
max(10, 20, 30);// Error, 应该有2个参数
可选参数和默认参数
// y是可选参数, z是默认参数
function max(x:number, y?:number, z = 5):void {
console.log(x + y + z);
}
max(10); // NAN
max(10, 20); // 35
max(10, 20, 30); // 60
max(10, undefined, 30); // NAN
max(10, null, 30); // 40
剩余参数
function max(x:number, ...num:number[]):void {
console.log(x, num);
}
max(10); // 10, []
max(10, 1, 2, 3, 4); // 10, [1, 2, 3, 4]