摘自维基百科 TypeScript前端
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个严格超集,并添加了可选的静态类型和使用看起来像基于类的面向对象编程语法操做 Prototype。C#的首席架构师以及Delphi和Turbo Pascal的创始人安德斯·海尔斯伯格参与了TypeScript的开发。git
GitHub 地址 github.com/microsoft/T…程序员
TypeScript 官网 www.typescriptlang.orggithub
如今有不少大型开源项目都开始使用 TypeScripttypescript
TypeScript 或许不久会成为前端的一个强制要求技能。在维护大型项目方面它很是有效。npm
下面开始行动起来。。。编程
npm install -g typescript
复制代码
编写基本代码json
// greeter.ts
function greeter(person: string) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
复制代码
编译代码数组
tsc greeter.ts
复制代码
let isDone: boolean = false;
复制代码
TypeScript 里的全部数字都是浮点数。 这些浮点数的类型是 number
。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。bash
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
复制代码
let name: string = "bob";
复制代码
// 方式1: 能够在元素类型后面接上 [],表示由此类型元素组成的一个数组
let list: number[] = [1, 2, 3];
// 方式2: 使用数组泛型,Array<元素类型>:
let list: Array<number> = [1, 2, 3];
复制代码
元组类型容许表示一个已知元素数量和类型的数组,各元素的类型没必要相同。 好比,你能够定义一对值分别为 string
和 number
类型的元组。
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error
复制代码
当访问一个越界的元素,会使用联合类型替代
x[3] = 'world';
x[6] = true; // Error, 布尔不是(string | number)类型
复制代码
enum
类型是对 JavaScript 标准数据类型的一个补充。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// 默认状况下,从0开始为元素编号, 也能够手动的指定成员的数值
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
复制代码
测试枚举类型
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
console.log(Color);
复制代码
编译以后
var Color;
(function (Color) {
Color[Color["Red"] = 1] = "Red";
Color[Color["Green"] = 2] = "Green";
Color[Color["Blue"] = 3] = "Blue";
})(Color || (Color = {}));
var c = Color.Green;
console.log(Color);
复制代码
执行的结果
{ '1': 'Red', '2': 'Green', '3': 'Blue', Red: 1, Green: 2, Blue: 3 }
复制代码
有时候,咱们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,好比来自用户输入或第三方代码库。 这种状况下,咱们不但愿类型检查器对这些值进行检查而是直接让它们经过编译阶段的检查。
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
复制代码
某种程度上来讲,void
类型像是与 any
类型相反,它表示没有任何类型。 当一个函数没有返回值时,你一般会见到其返回值类型是 void
。
声明一个 void
类型的变量没有什么大用,由于你只能为它赋予 undefined
和 null
let unusable: void = undefined;
复制代码
undefined
和 null
二者各自有本身的类型分别叫作 undefined
和 null
。 和 void
类似,它们的自己的类型用处不是很大
let u: undefined = undefined;
let n: null = null;
复制代码
默认状况下 null
和 undefined
是全部类型的子类型。 就是说你能够把 null
和 undefined
赋值给 number
类型的变量。
当你指定了 --strictNullChecks
标记,null
和 undefined
只能赋值给 void
和它们各自。
never
类型表示的是那些永不存在的值的类型。 例如,never
类型是那些老是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也多是 never
类型,当它们被永不为真的类型保护所约束时。
never
类型是任何类型的子类型,也能够赋值给任何类型;然而,没有类型是 never
的子类型或能够赋值给 never
类型(除了 never
自己以外)。 即便 any
也不能够赋值给 never
。
// 返回never的函数必须存在没法达到的终点
function error(message: string): never {
throw new Error(message);
}
复制代码
// 返回never的函数必须存在没法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
复制代码
object
表示非原始类型,也就是除 number
,string
,boolean
,symbol
,null
或 undefined
以外的类型。
使用 object
类型,就能够更好的表示像 Object.create
这样的API
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
复制代码
经过类型断言这种方式能够告诉编译器,“相信我,我知道本身在干什么”。 类型断言比如其它语言里的类型转换,可是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起做用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
复制代码
另外一个为 as
语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
复制代码
两种形式是等价的。 至于使用哪一个大多数状况下是凭我的喜爱;然而,当你在 TypeScript 里使用 JSX 时,只有 as
语法断言是被容许的。
可选属性在应用 “option bags” 模式时很经常使用,即给函数传入的参数对象中只有部分属性赋值了。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
复制代码
带有可选属性的接口与普通的接口定义差很少,只是在可选属性名字定义的后面加一个 ?
符号。
一些对象属性只能在对象刚刚建立的时候修改其值。 你能够在属性名前用 readonly
来指定只读属性
interface Point {
readonly x: number;
readonly y: number;
}
复制代码
能够经过赋值一个对象字面量来构造一个 Point
。 赋值后, x
和 y
不再能被改变了。
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
复制代码
TypeScript 具备 ReadonlyArray<T>
类型,它与 Array<T>
类似,只是把全部可变方法去掉了,所以能够确保数组建立后不再能被修改
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
复制代码
上面代码的最后一行,能够看到就算把整个 ReadonlyArray
赋值到一个普通数组也是不能够的。 可是你能够用类型断言重写
a = ro as number[];
复制代码
readonly
vs const
最简单判断该用 readonly
仍是 const
的方法是看要把它作为变量使用仍是作为一个属性。 作为变量使用的话用 const
,若作为属性则使用 readonly
。
为了使用接口表示函数类型,咱们须要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每一个参数都须要名字和类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
复制代码
可索引类型具备一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
复制代码
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
复制代码
也能够在接口中描述一个方法,在类里实现它
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
复制代码
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
复制代码
一个接口能够继承多个接口,建立出多个接口的合成接口。
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;
复制代码
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;
复制代码
接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了全部类中存在的成员,但并无提供具体实现同样。 接口一样会继承到类的 private 和 protected 成员。 这意味着当你建立了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// 错误:“Image”类型缺乏“state”属性。
class Image implements SelectableControl {
select() { }
}
class Location {
}
复制代码
在上面的例子里,SelectableControl
包含了 Control
的全部成员,包括私有成员 state
。 由于 state
是私有成员,因此只可以是 Control
的子类们才能实现 SelectableControl
接口。 由于只有 Control
的子类才可以拥有一个声明于 Control
的私有成员 state
,这对私有成员的兼容性是必需的。
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
复制代码
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
复制代码
默认为 public
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
复制代码
理解 private
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 错误: 'name' 是私有的.
复制代码
TypeScript使用的是结构性类型系统。 当咱们比较两种不一样的类型时,并不在意它们从何处而来,若是全部成员的类型都是兼容的,咱们就认为它们的类型是兼容的。
当咱们比较带有 private
或 protected
成员的类型的时候,状况就不一样了。 若是其中一个类型里包含一个 private成员,那么只有当另一个类型中也存在这样一个 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; // 错误: Animal 与 Employee 不兼容,若是 private 都变成 public 则不会报错
复制代码
理解 protected
protected
修饰符与 private
修饰符的行为很类似,但有一点不一样, protected
成员在派生类中仍然能够访问。
class Person {
// 这里若是变成 private 子类用 `this.name` 会报错
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' 的构造函数是被保护的.
复制代码
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
复制代码
咱们也能够建立类的静态成员,这些属性存在于类自己上面而不是类的实例上。
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
复制代码
抽象类作为其它派生类的基类使用。 它们通常不会直接被实例化。 不一样于接口,抽象类能够包含成员的实现细节。
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() // 错误: 方法在声明的抽象类中不存在
复制代码
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y; };
复制代码
// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };
// The parameters `x` and `y` have the type number
let myAdd: (baseValue: number, increment: number) => number = function(x, y) { return x + y; };
复制代码
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
复制代码
JavaScript 里,每一个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined。 在 TypeScript 里咱们能够在参数名旁使用 ?
实现可选参数的功能
function buildName(firstName: string, lastName?: string) {
// ...
}
复制代码
在 TypeScript 里,你能够把全部参数收集到一个变量里
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
复制代码
咱们须要一种方法使返回值的类型与传入参数的类型是相同的。这里,咱们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(arg: T): T {
return arg;
}
复制代码
咱们给 identity
添加了类型变量 T
。 T
帮助咱们捕获用户传入的类型(好比:number
),以后咱们就可使用这个类型。 以后咱们再次使用了 T 当作返回值类型。如今咱们能够知道参数类型与返回值类型是相同的了。 这容许咱们跟踪函数里使用的类型的信息。
使用泛型建立像 identity
这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当作是任意或全部类型。
若是咱们想同时打印出arg的长度。 咱们极可能会这样作
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
复制代码
如今假设咱们想操做 T
类型的数组而不直接是 T
。因为咱们操做的是数组,因此 .length
属性是应该存在的。 咱们能够像建立其它数组同样建立这个数组:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
复制代码
泛型函数的类型与非泛型函数的类型没什么不一样,只是有一个类型参数在最前面,像函数声明同样
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
复制代码
咱们也可使用不一样的泛型参数名,只要在数量上和使用方式上能对应上就能够。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
复制代码
还可使用带有调用签名的对象字面量来定义泛型函数(这块没看懂)
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
复制代码
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
复制代码
相比于操做 any
全部类型,咱们想要限制函数去处理任意带有 .length
属性的全部类型。 只要传入的类型有这个属性,咱们就容许,就是说至少包含这一属性。 为此,咱们须要列出对于 T
的约束要求。
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;
}
复制代码
enum Direction {
Up = 1,
Down,
Left,
Right
}
复制代码
使用枚举
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Princess Caroline", Response.Yes)
复制代码
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
复制代码
枚举是在运行时真正存在的对象
enum E {
X, Y, Z
}
function f(obj: { X: number }) {
return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
f(E);
复制代码
除了建立一个以属性名作为对象成员的对象以外,数字枚举成员还具备了 反向映射,从枚举值到枚举名字
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
复制代码
// 编译后
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"
复制代码
生成的代码中,枚举类型被编译成一个对象,它包含了正向映射( name
-> value
)和反向映射( value
-> name
)。 引用枚举成员总会生成为对属性访问而且永远也不会内联代码。
const
枚举为了不在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,咱们可使用 const
枚举。 常量枚举经过在枚举上使用 const
修饰符来定义。
const enum Enum {
A = 1,
B = A * 2
}
复制代码
常量枚举只能使用常量枚举表达式,而且不一样于常规的枚举,它们在编译阶段会被删除。常量枚举成员在使用的地方会被内联进来。 之因此能够这么作是由于,常量枚举不容许包含计算成员。
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
复制代码
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
复制代码
declare
declare enum Enum {
A = 1,
B,
C = 2
}
复制代码
外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。对于很是数的外部枚举而言,没有初始化方法时被当作须要通过计算的。
公众号,欢迎前往关注