TypeScript
基本类型
- 布尔值(boolean)
let isDone: boolean = false;
- 数值(number)
let decLiteral: number = 6;
- 字符串(string)
let myName: string = 'Tom';
- 空值(void)
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用
void表示没有任何返回值的函数: 声明一个void类型的变量没有什么用,因为你只能将它赋为undefined和null
let unusable: void = undefined;
- Null Undefined
与
void的区别是,undefined和null是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量:
// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;
而 void 类型的变量不能赋值给 number 类型的变量:
let u: void;
let num: number = u;
// Type 'void' is not assignable to type 'number'.
- 任意值(any)
any类型,则允许被赋值为任意类型。
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number;
接口(对象类型)
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
接口的可选属性
希望不完全匹配某种类型
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
使用 [propName: string] 定义了任意属性取 string 类型的值。
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
数组类型
- 使用「类型 + 方括号」来表示数组:
let fibonacci: number[] = [1, 1, 2, 3, 5];
- 使用数组泛型(Array Generic)
Array<elemType>来表示数组:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
- 接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
函数类型
在 JavaScript 中,有两种常见的定义函数的方式——函数声明和函数表达式:
- 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}
- 函数表达式(Function Expression)
let mySum = function (x, y) {
return x + y;
};
约束方法
function sum(x: number, y: number): number {
return x + y;
}
let mySum = function (x: number, y: number): number {
return x + y;
};
接口定义函数
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
可选参数
可选参数后面不允许再出现必需参数了
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
默认值
TypeScript 会将添加了默认值的参数识别为可选参数
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
剩余参数
ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数),用数组的类型来定义它
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
类型断言
用来手动指定一个值的类型。
值 as 类型
或
<类型>值
类型别名
类型别名用来给一个类型起个新名字。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
类型别名常用于联合类型。
高级类型
- 元组 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。 确定了索引对应的类型。
let tom: [string, number] = ['Tom', 25];
可以只赋值其中一项:
let tom: [string, number];
tom[0] = 'Tom';
枚举
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:
相当于键值对互相映射的对象。
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
我们也可以给枚举项手动赋值:
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
类
基本概念
- 类:包含属性和方法,是事物的抽象
- 对象:类的实例
- 存取器:改变属性的读取和赋值行为
- 修饰符:限定成员或类型的性质
- 抽象类:其他类继承的基类,不允许被实例化,类的方法在子类中被实现(类的地基。自身未实现)
- 接口:不同类之间共有的属性或方法,可以抽象成接口。一个类只能继承自另一个类,但可以实现那多个接口(共有逻辑的抽离)
基本操作
- 属性和方法
使用
class定义类,使用constructor定义构造函数。
通过 new 生成新实例的时候,会自动调用构造函数。
class Animal {
name;
constructor(name) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
- 继承
使用
extends关键字实现继承,子类中使用super关键字来调用父类的构造函数和方法。
class Cat extends Animal {
constructor(name) {
super(name); // 调用父类的 constructor(name)
console.log(this.name);
}
sayHi() {
return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
}
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
- 静态方法(通过类来调用,而非通过实例调用)
使用
static修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
- 修饰符
public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public的private修饰的属性或方法是私有的,不能在声明它的类的外部访问protected修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。(在使用泛型的函数中要在函数后说明有哪些泛型要用,有点类似于变量声明)
在此段代码中。泛型的作用有点类似变量,同一个x,数值未定,但是在用到的每一个地方都是这个x。
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
泛型接口
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
注意,此时在使用泛型接口的时候,需要定义泛型的类型。
装饰器·
是一个方法,可以注入到类、方法、属性上等进行扩展。
本质上接受一些相关参数,对此参数做出操作来影响对应的类或方法等
装饰器分类
- 装饰器工厂
//装饰器工厂(可以传参)
function Greeter(greeting: string) {
return function (target: Function) {
target.prototype.greet = function (): void {
console.log(greeting);
};
};
}
//声明装饰器,如此可在下方类扩展了Greter的方法和属性
@Greeter('hello')
class Greeting {
constructor() {
// 内部实现
}
}
- 类装饰器
它接收一个参数: target: TFunction - 被装饰的类
//类装饰器,无法传参
function Greeter(target:Function) {
target.prototype.greet = function (): void {
console.log(greeting);
};
}
//声明装饰器,如此可在下方类扩展了Greter的方法和属性
@Greeter
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello TS!';
装饰器组合
结合起来的装饰器,首先会从上至下执行所有装饰器工厂,拿到所有装饰器,然后从下至上的执行所有装饰器。
- 属性装饰器 它接收两个参数:
target: Object - 被装饰的类 propertyKey: string | symbol - 被装饰类的属性名
function logProperty(target: any, key: string) {
delete target[key];
const backingField = "_" + key;
Object.defineProperty(target, backingField, {
writable: true,
enumerable: true,
configurable: true
});
// property getter
const getter = function (this: any) {
const currVal = this[backingField];
console.log(`Get: ${key} => ${currVal}`);
return currVal;
};
// property setter
const setter = function (this: any, newVal: any) {
console.log(`Set: ${key} => ${newVal}`);
this[backingField] = newVal;
};
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Person {
@logProperty
public name: string;
constructor(name : string) {
this.name = name;
}
}
const p1 = new Person("kylee");
p1.name = "xiaozhang";
-
方法装饰器 它接收三个参数:
-
target: Object - 被装饰的类
-
propertyKey: string | symbol - 方法名
-
descriptor: TypePropertyDescriptor - 属性描述符
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
let originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("wrapped function: before invoking " + propertyKey);
let result = originalMethod.apply(this, args);
console.log("wrapped function: after invoking " + propertyKey);
return result;
};
}
class Task {
@log
runTask(arg: any): any {
console.log("runTask invoked, args: " + arg);
return "finished";
}
}
let task = new Task();
let result = task.runTask("learn ts");
console.log("result: " + result);
-
参数装饰器 它接收三个参数:
-
target: Object - 被装饰的类
-
propertyKey: string | symbol - 方法名
-
parameterIndex: number - 方法中参数的索引值
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`);
}
class Greeter {
greeting: string;
constructor(@Log phrase: string) {
this.greeting = phrase;
}
}