Typescript知识梳理
概述
TypeScript简称TS,具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。通过TS能够生成JS,TS是结构性语言,能够类似C#和Java那样,面向对象编程。可以采用VS或者VS Code作为编辑器。
基本类型
布尔类型:let isDone: boolean = false;
数字类型:let decLiteral: number = 6;//decLiteral: number =0xf00d;支持16进制、10进制
字符串类型:let name: string = "bob";//可以使用模板,实例如let sentence: string = `Hello, my name is ${ name }`
数组类型:let list: number[] = [1, 2, 3]; 或 let list: Array<number> = [1, 2, 3];
元组类型:let x: [string, number]=["Hello",87];
枚举类型:enum Color {Red = 1, Green, Blue}; let c: Color = Color.Green;
任意值类型:let notSure: any = 4; notSure='Hello' ;
空类型: let unusable: void = undefined;
接口(interface)
TS中的接口不同于C#和Java有着明确的定义去规划继承接口的类。TS是结构性语言,更多的通过结构比对,来达到类型的兼容。
接口描述属性:
interface LabelledValue { label: string;
size?: number } function printLabel(labelledObj: LabelledValue) {
if(labelledObj.size)
console.log('尺寸:'+labelledObj.size); console.log(labelledObj.label); } let myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
我们在这里并不能像在其它语言里一样,说传给 printLabel 的对象实现了这个接口。我们只会去关注值的外形。 只要传入的对象满足上面提到的必要条件,那么它就是被允许的。还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。size?:number标识该参数是可选类型,判断是否传入值可以通过if(lablelledObj.size)来确定;
接口描述函数:
接口也可以描述函数类型。为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。函数的参数名不需要与接口里定义的名字相匹配。
interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source_T: string, subString_T: string)
{ let result = source.search(subString); if (result == -1) { return false; } else { return true; } }
接口描述类:
类是具有两个类型的:静态部分的类型和实例的类型;
//**********************报错的代码部分******************************
//用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误,因为当一个类实现了一个接口时,只对其实例部分进行类型检查。
//constructor存在于类的静态部分,所以不在检查的范围内 interface ClockConstructor { new (hour: number, minute: number); } class Clock implements ClockConstructor { currentTime: Date; constructor(h: number, m: number) { } }
//*****************修改之后的代码部分********************************
//所以针对上面错误,我们将接口分为2部分,静态部分和实例部分接口。实例部分,我们采用继承来实现;直接操作静态部分 interface Clock
{ new (hour: number, Minute: number): ClockFun; } interface ClockFun
{ ShowData(): string; } class MyClock implements ClockFun { _hour: number; _minu: number; constructor(hour: number, Minu: number) { this._hour = hour; this._minu = Minu; } public ShowData(): string { return `当前时间:${this._hour}点${this._minu}分`; } } //静态部分的操作 function MyCeate2() { let datm: Clock = MyClock; let Namv2 = new datm(123, 5); Namv2.ShowData(); }
接口扩展:
接口扩展包括接口之间的继承extends和接口继承类,接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。
class OtherProClass{
public height:numner;
}
interface Shape { color: string; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke,OtherProClass { sideLength: number; } let square = <Square>{}; square.color = "blue"; square.sideLength = 10; square.penWidth = 5.0;
square.height=100;
接口内容的混合使用:
接口即可以标识字段、方法、签名方法、类,这些标识是可以混合定义在一个接口里面,由于TS中接口的含义广,所以命名是不建议用I开头,来标识一个接口含义;
interface Counter { (start: number): string; interval: number; reset(): void; } function getCounter(): Counter
{ let counter = <Counter>function (start: number) { }; counter.interval = 123; counter.reset = function () { }; return counter; } let c = getCounter(); c(10); c.reset(); c.interval = 5.0;
联合类型
联合类型表示一个值可以是几种类型之一。 我们用竖线( | )分隔每个类型,所以 number | string | boolean 表示一个值可以是 number , string ,或 boolean 。
interface Bird { fly(); layEggs(); } interface Fish { swim(); layEggs(); } function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet(); pet.layEggs(); // okay pet.swim(); // errors //为了让这码代码工作,我们要使用类型断言 let pet2 = getSmallPet(); if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else{ (<Bird>pet).fly(); }
自定义的类型保护
TypeScript里的类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型断言;
//pet is Fish 就是类型断言。一个断言是 parameterName is Type 这种形式, parameterName 必须是来自于当前函数签名里的一个参数名 function isFish(pet: Fish | Bird): pet is Fish { return (<Fish>pet).swim !== undefined; } //调用断言 if (isFish(pet) { pet.swim(); } else { pet.fly(); }
使用typeof 类型保护,来实现断言,这些 typeof 类型保护只有2个形式能被识别: typeof v ==="typename" 和 typeof v !== "typename" , "typename" 必须是 "number" , "string" , "boolean" 或 "symbol"
function isNumber(x: any): x is number
{ return typeof x === "number"; }
instanceof 类型保护,instanceof 类型保护是通过其构造函数来细化其类型。
if (myDataNum instanceof MyClock) { console.log("输出数据类型"); }
类型别名
类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
type XCoord = number; type YCoord = number; type XYCoord = { x: XCoord; y: YCoord }; type XYZCoord = { x: XCoord; y: YCoord; z: number }; type Coordinate = XCoord | XYCoord | XYZCoord; type CoordList = Coordinate[]; let coord: CoordList = [{ x: 10, y: 10}, { x: 0, y: 42, z: 10 }]
type Container<T> = { value: T };//泛类型
类(Class)
类是通用的代码块,我们在引用任何一个类成员的时候都用了 this 。 它表示我们访问的是类的成员。构造函数不同于C#和Java,采用 constructor来标识;类可以实现封装、继承和多态;子类可以重写父类的方法;在TypeScript里,每个成员默认为 public 的。
当成员被标记成 private 时,它就不能在声明它的类的外部访问。TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从哪儿来的,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。然而,当我们比较带有 private 或 protected 成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private 成员,那么只有当另外一个类型中也存在这样一个 private 成, 并且它们是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected 成员也使用这个规则。protected 修饰符与 private 修饰符的行为很相似,但有一点不同, protected 成员在派生类中仍然可以访问。
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } } class Employee { private name: string; constructor(theName: string) { this.name = theName; } } let animal = new Animal("Goat"); let rhino = new Rhino(); let employee = new Employee("Bob"); animal = rhino; animal = employee;//由于定义了Private成员,该出的成员定义不是出于同一处,所以赋值失败
属性参数
通过构造函数里面,用protected、private和public修改参数,可以定义类属性;
class Animal { constructor(private name: string) { } move(distanceInMeters: number) { console.log(`${this.name} moved ${distanceInMeters}m.`); } }
存取器
TypeScript支持getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
class Employee { private _fullName: string; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (passcode && passcode == "secret passcode") { this._fullName = newName; } else { console.log("Error: Unauthorized update of employee!"); } } }
抽象类是供其它类继承的基类。 他们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。类分为静态部分,用static修改,也分为实例部分;静态部分对象,可以直接通过类来访问;
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; greeterMaker.standardGreeting = "Hey there!"; let greeter2:Greeter = new greeterMaker(); console.log(greeter2.greet());
命名空间和模块
模块的引用
import x from "..."; 或 import x = require("...") 里面的 ... ,等等)来定位模块的类型信息的。“内部模块”现在叫做“命名空间”。任何包含顶级 import 或者 export 的文件都被当成一个模块。
导出声明:任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加 export 关键字来导出。默认导出用default
//声明导出接口 export interface StringValidator { isAcceptable(s: string): boolean; } //声明导出语句 class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } export { ZipCodeValidator }; export { ZipCodeValidator as mainValidator };
导入:模块的导入操作与导出一样简单。 可以使用以下 import 形式之一来导入其它模块中的导出内容。
//可以对导入内容重命名 import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV(); //将整个模块导入到一个变量,并通过它来访问模块的导出部分 import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator();
若要导入一个使用了 export = 的模块时,必须使用TypeScript提供的特定语法 import let = require("module") 。
VS中TS解决方案的配置
配置内容如图所示:
参考博客