TypeScript笔记
基础类型
1、指定类型:let list: Array<number> = [1, 2, 3];
2、元组 Tuple:元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
3、枚举 enum:从名取到值:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green; // 2
从值取到名:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]; // 'Green'
4、any:区别于Object
类型,Object
类型的变量只是允许你给它赋任意值,但是却不能够在它上面调用任意的方法。
5、void:没有任何类型,常用于当一个函数没有返回值时。
6、never:表示那些永不存在的值的类型。 例如, 总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
7、类型断言:好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。
“尖括号”语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
as 语法(JSX只允许as)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
接口
属性检查
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型符合。
如果将上述方法调用改成printLabel({size: 10, label: "Size 10 Object"});
将会报错:
Argument of type ‘{ size: number; label: string; }’ is not assignable to parameter of type ‘LabelledValue’.
Object literal may only specify known properties, and ‘size’ does not exist in type ‘LabelledValue’.
52 printLabel({size: 10, label: “Size 10 Object”});
对象字面量会被特殊对待而且会经过额外属性检查,当将它们赋值给变量或作为参数传递的时候, 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
除了上述示例,赋值给另一个对象避开检查,还可以通过类型断言或字符串索引签名避开:
printLabel({size: 10, label: "Size 10 Object"} as LabelledValue);
interface LabelledValue { label: string; [propName: string]: any;}
可选属性/只读属性
interface SquareConfig {
color?: string;
width?: number;
readonly id: string;
}
function createSquare(config: SquareConfig): {color: string; area: number; id: string;} {
let newSquare = {color: "white", area: 100, id:""};
if (config.id) {
newSquare.id = config.id;
config.id = '1'; //error
//Cannot assign to 'id' because it is a read-only property.
}
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black",id: '123'});
在属性定义的后面加一个?
符号代表可选属性,在属性定义前加readonly
代表只读属性。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
return result > -1;
}
索引类型
interface StringArray { [index: number]: string; }
表示用 number
去索引StringArray
时会得到string
类型的返回值。
实现接口的类
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number) {
let date=new Date();
date.setHours(h);
this.setTime(date);
}
}
let c = new Clock(1);
console.log(c.currentTime);
ClockInterface
接口中定义了一个currentTime
属性,一个setTime
方法,Clock
实现了ClockInterface
接口中的setTime
方法。
接口继承
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
接口继承接口:用extends
,可继承多个接口,用“,”逗号分隔。
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class Img implements SelectableControl {
select() { } // error, missing "state"
}
接口继承类:会继承到类的private和protected成员,这个接口将只能被这个类或其子类所实现。
类
继承
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);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
在派生类的构造器中必须调用 super()
,它会执行基类的构造函数。
在构造函数里访问 this
的属性之前必须调用 super()
。
修复符
1、默认public
2、private
属性不能再声明它的类外部访问。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name;
// Property 'name' is protected and only accessible within class 'Person' and its subclasses.
3、protected
与private类似,但在派生类中可以访问。
构造函数也可以被标记成 protected
,意味这个类不能在包含它的类外被实例化,但能被继承。
class Person2 {
protected name: string;
constructor(name: string) { this.name = name; }
// protected constructor(theName: string) { this.name = theName; }
}
class Employee extends Person2 {
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");
console.log(howard.getElevatorPitch()); // Hello, my name is Howard and I work in Sales.
// console.log(howard.name);
// Property 'name' is protected and only accessible within class 'Person' and its subclasses.
// let john = new Person2("John");
// Constructor of class 'Person2' is protected and only accessible within the class declaration.
4、readonly
将属性设置为只读,只读属性必须在声明时或构造函数里被初始化。
class Octopus {
readonly name: string;
constructor (theName: string) {
this.name = theName;
}
}
let o=new Octopus('a');
console.log(o.name);
上面的例子可以写成下面这样:
class Octopus1 {
constructor(readonly name: string) {
}
}
let o1=new Octopus1('b');
console.log(o1.name);
参数属性:通过给构造函数参数前面添加一个访问限定符,把声明和赋值合并。
5、static
静态属性,只能通过类名.XX来访问,静态属性不存在于实例。
存取器
class Test {
private _name: string;
get name(): string {
return this._name;
}
set name(newName: string) {
this._name = newName;
}
}
let test = new Test();
test.name='set name';
console.log(test.name);
报错:
Accessors are only available when targeting ECMAScript 5 and higher.
这是因为存取器要求你将编译器设置为输出ECMAScript 5或更高,可用tsc -t es5 greeter.ts
命令编译解决。
抽象类
1、abstract
关键字用于定义抽象类
和在抽象类内部定义抽象方法
。
2、抽象类做为其它派生类的基类使用,不能被实例化。
3、抽象类可以包含成员的实现细节,但抽象方法不包含具体实现并且必须在派生类中实现。
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'); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // 允许创建一个对抽象类型的引用,意思是Department类的实例的类型是Department
// department = new Department();
// error: Cannot create an instance of an abstract class.
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
// department.generateReports();
// error: Property 'generateReports' does not exist on type 'Department'.
函数
1、参数名旁使用 ?
实现可选参数的功能:
function buildName(firstName: string, lastName?: string) {}
2、可选参数必须跟在必须参数后面。
3、可以为参数提供一个默认值:
function buildName(firstName: string, lastName = "Smith") {}
4、在所有必须参数后面的带默认初始化的参数都是可选的。
5、带默认值的参数不需要放在必须参数的后面, 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined
值来获得默认值。
6、剩余参数:
function buildName(firstName: string, ...restOfName: string[]) {}
7、箭头函数能保存函数创建时的 this
值,而不是调用时的值。
8、为同一个函数提供多个函数类型定义来进行函数重载。
let suits = ["hearts", "spades", "clubs", "diamonds"];
// 两个重载
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
泛型
function identity<T>(arg: T): T {
return arg;
}
为确保返回值的类型与传入参数的类型是相同的,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。
泛型约束,例:具有length属性的类型
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
枚举
TypeScript支持数字的和基于字符串的枚举。
enum Direction {
Up = 1,
// Up, // 默认从0开始
Down, //2
Left, //3
Right //4
}
高级类型
1、交叉类型:把现有的多种类型叠加到一起成为一种类型。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
2、联合类型:
联合类型表示一个值可以是几种类型之一,用竖线 |
分隔。
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
3、类型保护:定义一个函数的返回值是一个类型谓词parameterName is Type
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
4、使用类型断言手动去除 null
或 undefined
,语法是添加 !
后缀:
identifier!
------从 identifier的类型里去除了 null和 undefined
5、类型别名:
起别名不会新建一个类型,它创建了一个新名字来引用那个类型
类型别名不能被 extends
和 implements
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
6、索引类型查询操作符:keyof T
,对于任何类型 T
, keyof T
的结果为 T
上已知的公共属性名的联合。
7、索引访问操作放:T[K]
Symbols
1、symbols是不可改变且唯一的:
let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
2、symbols也可以被用做对象属性的键:
let sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
迭代器和生成器
1、for...of
:迭代对象的键对应的值
let someArray = [1, "string", false];
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}
2、for...in
:迭代的是对象的键的列表
for (let entry in someArray) {
console.log(entry); // "0", "1", "2"
console.log(typeof entry); //string
}
3、for..in
可以操作任何对象,它提供了查看对象属性的一种方法,但是 for..of
关注于迭代对象的值
let pets = new Set(["Cat", "Dog", "Hamster"]);
//内置对象Map和Set已经实现了Symbol.iterator方法,让我们可以访问它们保存的值
pets["species"] = "mammals";
for (let pet in pets) {
console.log(pet); // "species"
}
for (let pet of pets) {
console.log(pet); // "Cat", "Dog", "Hamster"
}
模块
1、export
任何声明(比如变量,函数,类,类型别名或接口)都能够通过export导出。
导出重命名:
export oldname as newname
一个模块可以包裹多个模块,把他们导出的内容联合在一起:
export * from "module"
2、import
可重命名,可导入整个模块:
import * as validator from "./ZipCodeValidator";
3、default
每个模块都可以有一个default
导出。
类和函数声明可以直接被标记为默认导出,default
导出也可以是一个值。
4、export = 和 import = require()
若使用export =
导出一个模块,则必须使用import module = require("module")
导入。
5、模块(外部模块):
declare module "hot-new-module";
export as namespace mathLib;
模块里不要使用命名空间。
6、多文件中的命名空间(内部模块):
多个文件使用同一个命名空间,加入引用标签来告诉编译器文件之间的依赖关联:
/// <reference path="ZipCodeValidator.ts" />
不要用/// <reference>
引用模块。
编译为一个输出文件:
tsc --outFile sample.js Test.ts
7、命名空间别名:import q = x.y.z
声明合并
1、合并接口:
后来的接口重载出现在靠前位置。
如果签名里有一个参数的类型是单一的字符串字面量,那么它将会被提升到重载列表的最顶端。
参考
相关学习资料
《TypeScript-一种思维方式》
《TypeScript Handbook》
《Advanced Static Types in TypeScript》