Typescript 入门基础篇(二)
Typescript 入门基础篇(一)传送门。
Typescript 入门基础篇(三)传送门。
上一篇说到了存储器
抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。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'); // 在派生类的构造函数中必须调用 super()
}
// 派生类中必须实现抽象基类中的抽象方法
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在
高级技巧
- 构造函数
在
Typescript中声明一个类的时候,实际上声明了很多东西,首先是类的实例的类型。构造函数的类型包含了类的所有静态属性。换个角度说,我们可以认为类具有实例部分和静态部分这两个部分。
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());
let greeterMaker: typeof Greeter = Greeter; // typeof Greeter 意思为取Greeter类的类型。而不是实例的类型。或者更确切的说,‘告诉我Greeter标识符的类型’,也就是构造函数的类型。
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
- 把类当做接口使用 类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
函数
这里依然只列出与JS不一样的地方
# 完整的函数类型书写
function add (x : number, y : number) : number {
return x + y;
}
let orgAdd : ( x : number, y : number ) => number = function ( x : number, y : number ) : number { return x + y; }
参数类型的名字不必与参数名相同,只要类型是匹配的就好了,就认为它是有效的参数类型。
# 推断类型(按上下文归类)
赋值语句一边指定了类型,另一边没有指定类型,这个时候typescript编译器会自动识别出来。
# 可选参数
function arguments( fir : string, sec ?: string ) : string {
if(sec)
return fir + '' + sec;
else
return fir;
}
区别js和ts的可选参数
js中的参数都是可选的,不传的时候则为undefined,在ts里在参数旁边使用?可以实现可选参数的功能。
可选参数必须跟在必须参数后面
# 剩余参数
js中如果操作多个参数可以使用arguments,而ts中你可以把所有参数收集到一个变量里。
function rest ( fir : string, ...rest : string[] ) : string {
return fir + ' ' + rest.join(' ');
}
函数的类型只是由参数类型和返回值组成的。 函数中使用的捕获变量不会体现在类型里。 实际上,这些变量是函数的隐藏状态并不是组成API的一部分。
this
这里单独把
this拿出来说,因为比较重要。如果你还不懂js里的this,那么先跳转传送门Understanding JavaScript Function Invocation and "this";
# 提供显式的`this`
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
现在TypeScript知道createCardPicker期望在某个Deck对象上调用。 也就是说 this是Deck类型的,而非any,因此--noImplicitThis不会报错了。
# 重载
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);
为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
泛型
# 举个栗子
function identity<T>(arg: T): T {
return arg;
}
这里的T为类型变量,表示类型而不是值。
这里T帮助ts捕捉用户输入的类型,之后就可以使用这个类型。使用T当做返回值类型,这样可以知道传入值和返回值类型是相同的。这样就可以跟踪函数里使用的类型的信息。这个就叫做泛型。可以适用于多个类型,不会丢失信息。保持准确性,传入数值类型并返回数值类型。
# 使用泛型函数
1. 传入所有参数,包含类型参数
let output = identity<string>('mystring');
2. 利用类型推论 -- 即编译器会根据传入的参数自动的帮助我们确定T的类型
let output = identity('mystring');
类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
# 泛型接口
interface genFn<T> {
( arg : T ) : T;
};
let ide : genFn<number> = identity;
# 泛型类
class Gen <T> {
zero : T;
add : ( x : T, y : T ) => T;
}
let gen = new Gen<Number>();
泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
# 泛型约束
利用接口来描述约束条件,然后使用这个接口和extends关键字实现约束
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;
}
这时的泛型函数被定义了约束,因此它不再是适用于任何类型。
# 泛型约束中使用类型参数
function getProperty(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
# 在泛型里使用类类型
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
使用原型属性推断并约束构造函数与类实例的关系。
你也可以关注我的公众号