- 和 JS 中一样,TS 中的数字都是浮点数。浮点数的类型是 number。
- 类型推断机制。要把 TS 中的变量当做有类型,虽然实际上并没有。在 java 中,如果你给一个变量初始化为字符串,那么它便只能是字符串了。TS 中的类型推断就类似这样,当你把一个变量赋值成了字符串,那么 TS 的类型推断就会把这个变量当做字符串类型变量,后续你也应该把这个变量当做字符串类型,如果你又把一个 number 赋值给该变量,那么 TS 就会 warn 你不能这么操作(你是不是误操作了?),但是 TS 也只是 warn,它还是会给你编译出相应的 js 代码,在 java 中你这么操作可就是 error 了。
- 字符串模板字面量。TS 中模板字面量的行为和 ES6 中是一致的?
- TS 定义数组。
- 元素类型后面接上 []
- 数组泛型, Array<元素类型>
- 元组(tuple),元组是一个特殊数组,该数组的每个元素的类型都是确定的。
let x: [string, number]
- 枚举类型(enum),JS 中没有枚举类型。enumerate,枚举类型还是有点实用的。语法糖~ 枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
alert(colorName); // 显示'Green'因为上面代码里它的值是2
- any 类型。如其名,any,任何类型 = 没有类型。移除 TS 类型检查。
- void 类型。只能为 void 类型的变量赋 undefined 和 null 。
- null 和 undefined。默认情况下,null 和 undefined 是所有类型的子类型。就是说你可以把 null 和 undefined 赋值给 number 类型的变量。
然而,当你编译时指定了--strictNullChecks
标记, null 和 undefined 只能赋值给 void 和它们自己。 - never 类型。为什么会有这么奇葩的类型。never 类型表示的是那些永不存在的值的类型。例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式。never 类型是任何类型的子类型,也可以赋值给任何类型; 然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never 。
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
- 类型断言。通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。“我比你更清楚这变量,我就是要这样做”。。
类型断言有两种形式。 其一是 尖括号 语法:
let someValue: any = "this is a string";
let strLength: number = (someValue).length;
另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
两种形式是等价的。然而,当你在 TypeScript 里使用 JSX 时,只有 as语法断言是被允许的。
- TS 中的解构和 ES6 基本一致。
- 定义函数返回值类型。
函数():类型 {}
TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。 - TS 中每个函数参数都是必须的。传递给一个函数的参数必须与函数期望的参数个数一致。
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
// error, too few parameters
let result1 = buildName("Bob");
// error, too many parameters
let result2 = buildName("Bob", "Adams", "Sr.");
// 这种方式是正确的
let result3 = buildName("Bob", "Adams");
- 可选参数。在TypeScript里我们可以在参数名旁使用 ?实现可选参数的功能。所以便有了这样的写法
?:
。坑爹啊,还以为?:
是一个特殊的操作符。**注意: 可选参数必须跟在必须参数后面。 ** - 剩余参数。和 ES6 一致。
- TS 提供函数重载功能。TypeScript的函数重载只有一个函数体,也就是说无论声明多少个同名且不同签名的函数,它们共享一个函数体,在调用时会根据传递实参类型的不同,利用流程控制语句控制代码的执行。
啊?这?有什么用?无非是因为又类型限制,,所以 TS 才需要重载,这算是糟粕吧。这种情况还是直接用 any 类型吧。 - 接口。在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
- 只读属性。可以在属性名前用 readonly来指定只读属性。
- TypeScript 具有 ReadonlyArray
类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
上面代码的最后一行,可以看到就算把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:
a = ro as number[];
- readonly, const使用时机.做为变量使用的话用 const ,若做为属性则使用 readonly 。
- 接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
- 实现接口。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) { }
}
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。
- 继承接口。extends。和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
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;
又出现了神奇的东西,
,这样就可以创建一个空的 Square 对象吗?
- 在TypeScript里,成员都默认为 public。
- 当成员被标记成 private时,它就不能在声明它的类的外部访问。
- protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
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");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误
构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 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"); // 错误: 'Person' 的构造函数是被保护的.
- 参数属性。参数属性通过给构造函数参数添加一个访问限定符来声明。 使用 private限定一个参数属性会声明并初始化一个私有成员;对于 public和 protected来说也是一样。
- 带有 get不带有 set的存取器自动被推断为 readonly。
- 静态属性。static。
- 抽象类。abstract。抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 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(); // 错误: 方法在声明的抽象类中不存在
- 泛型。
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
下面来创建 identity函数。 这个函数会返回任何传入它的值。 - 为什么需要泛型,而不用 any ?
非泛型例子1:
function identity(arg: number): number {
return arg;
}
非泛型例子2: 使用any类型来定义函数
function identity(arg: any): any {
return arg;
}
使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
- 泛型的例子
我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity(arg: T): T {
return arg;
}
我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。 不同于使用 any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。
- 泛型接口。
interface GenericIdentityFn {
(arg: T): T;
}
我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary
interface GenericIdentityFn {
(arg: T): T;
}
function identity(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
注意,我们的示例做了少许改动。 不再描述泛型函数,而是把非泛型函数签名作为泛型类型一部分。 当我们使用 GenericIdentityFn的时候,还得传入一个类型参数来指定泛型类型(这里是:number),锁定了之后代码里使用的类型。 对于描述哪部分类型属于泛型部分来说,理解何时把参数放在调用签名里和何时放在接口上是很有帮助的。
- 泛型类。泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。
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; };
GenericNumber类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
- 注意,无法创建泛型枚举和泛型命名空间。
- 枚举类型。
- 命名空间。namespace。TypeScript里使用命名空间(之前叫做“内部模块”)来组织你的代码。
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
}
例子里,把所有与验证器相关的类型都放到一个叫做Validation的命名空间里。 因为我们想让这些接口和类在命名空间之外也是可访问的,所以需要使用 export。 相反的,变量 lettersRegexp和numberRegexp是实现的细节,不需要导出,因此它们在命名空间外是不能访问的。 在文件末尾的测试代码里,由于是在命名空间之外访问,因此需要限定类型的名称,比如 Validation.LettersOnlyValidator。