摘自維基百科 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
}
複製代碼
外部枚舉和非外部枚舉之間有一個重要的區別,在正常的枚舉裏,沒有初始化方法的成員被當成常數成員。對於很是數的外部枚舉而言,沒有初始化方法時被當作須要通過計算的。
公衆號,歡迎前往關注