久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

最全TypeScript 入門基礎(chǔ)教程,,看完就會

 悅光陰 2021-10-07

想學習 TypeScript 的小伙伴看過來,本文將帶你一步步學習 TypeScript 入門相關(guān)的十四個知識點,,詳細的內(nèi)容大綱請看下圖:

 

img

 

一,、TypeScript 是什么

 

TypeScript 是一種由微軟開發(fā)的自由和開源的編程語言。它是 JavaScript 的一個超集,,而且本質(zhì)上向這個語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?/span>

 

TypeScript 提供最新的和不斷發(fā)展的 JavaScript 特性,,包括那些來自 2015 年的 ECMAScript 和未來的提案中的特性,比如異步功能和 Decorators,,以幫助建立健壯的組件,。下圖顯示了 TypeScript 與 ES5、ES2015 和 ES2016 之間的關(guān)系:

 

img

 

1.1 TypeScript 與 JavaScript 的區(qū)別

 

img

 

1.2 獲取 TypeScript

 

命令行的 TypeScript 編譯器可以使用 Node.js 包來安裝,。

 

1.安裝 TypeScript

 

 $ npm install -g typescript

 

2.編譯 TypeScript 文件

 

 $ tsc helloworld.ts# helloworld.ts => helloworld.js

 

當然,,對于剛?cè)腴T TypeScript 的小伙伴,也可以不用安裝 typescript,,而是直接使用線上的 TypeScript Playground 來學習新的語法或新特性,。

 

二,、TypeScript 基礎(chǔ)類型

 

2.1 Boolean 類型

 

 let isDone: boolean = false;// ES5:var isDone = false;

 

2.2 Number 類型

 

 let count: number = 10;// ES5:var count = 10;

 

String 類型

 

 let name: string = "Semliker";// ES5:var name = 'Semlinker';

 

2.4 Array 類型

 

 let list: number[] = [1, 2, 3];// ES5:var list = [1,2,3];let list: Array<number> = [1, 2, 3]; // Array<number>泛型語法// ES5:var list = [1,2,3];

 

2.5 Enum 類型

 

使用枚舉我們可以定義一些帶名字的常量。 使用枚舉可以清晰地表達意圖或創(chuàng)建一組有區(qū)別的用例,。 TypeScript 支持數(shù)字的和基于字符串的枚舉,。

 

1.數(shù)字枚舉

 

 enum Direction {  NORTH,  SOUTH,  EAST,  WEST,}let dir: Direction = Direction.NORTH;

 

默認情況下,NORTH 的初始值為 0,,其余的成員會從 1 開始自動增長,。換句話說,,Direction.SOUTH 的值為 1,,Direction.EAST 的值為 2,Direction.WEST 的值為 3,。上面的枚舉示例代碼經(jīng)過編譯后會生成以下代碼:

 

 ?
 enum Direction {
   NORTH,
   SOUTH,
   EAST,
   WEST,
 }
 ?
 let dir: Direction = Direction.NORTH;

 

當然我們也可以設(shè)置 NORTH 的初始值,,比如:

 

 enum Direction {  NORTH = 3,  SOUTH,  EAST,  WEST,}

 

2.字符串枚舉

 

在 TypeScript 2.4 版本,允許我們使用字符串枚舉,。在一個字符串枚舉里,,每個成員都必須用字符串字面量,或另外一個字符串枚舉成員進行初始化,。

 

 enum Direction {  NORTH = "NORTH",  SOUTH = "SOUTH",  EAST = "EAST",  WEST = "WEST",}

 

以上代碼對于的 ES5 代碼如下:

 

 ?
 "use strict";
 var Direction;
 (function (Direction) {
     Direction["NORTH"] = "NORTH";
     Direction["SOUTH"] = "SOUTH";
     Direction["EAST"] = "EAST";
     Direction["WEST"] = "WEST";
 })(Direction || (Direction = {}));

 

3.異構(gòu)枚舉

 

異構(gòu)枚舉的成員值是數(shù)字和字符串的混合:

 

 enum Enum {  A,  B,  C = "C",  D = "D",  E = 8,  F,}

 

以上代碼對于的 ES5 代碼如下:

 

 ?
 enum Enum {
   A,
   B,
   C = "C",
   D = "D",
   E = 8,
   F,
 }

 

通過觀察上述生成的 ES5 代碼,,我們可以發(fā)現(xiàn)數(shù)字枚舉相對字符串枚舉多了 “反向映射”:

 

 console.log(Enum.A) //輸出:0console.log(Enum[0]) // 輸出:A

 

2.6 Any 類型

 

在 TypeScript 中,任何類型都可以被歸為 any 類型,。這讓 any 類型成為了類型系統(tǒng)的頂級類型(也被稱作全局超級類型)。

 

 let notSure: any = 666;notSure = "Semlinker";notSure = false;

 

any 類型本質(zhì)上是類型系統(tǒng)的一個逃逸艙。作為開發(fā)者,,這給了我們很大的自由:TypeScript 允許我們對 any 類型的值執(zhí)行任何操作,,而無需事先執(zhí)行任何形式的檢查,。比如:

 

 let value: any;value.foo.bar; // OKvalue.trim(); // OKvalue(); // OKnew value(); // OKvalue[0][1]; // OK

 

在許多場景下,這太寬松了,。使用 any 類型,,可以很容易地編寫類型正確但在運行時有問題的代碼,。如果我們使用 any 類型,,就無法使用 TypeScript 提供的大量的保護機制,。為了解決 any 帶來的問題,,TypeScript 3.0 引入了 unknown 類型,。

 

2.7 Unknown 類型

 

就像所有類型都可以賦值給 any,,所有類型也都可以賦值給 unknown。這使得 unknown 成為 TypeScript 類型系統(tǒng)的另一種頂級類型(另一種是 any)。下面我們來看一下 unknown 類型的使用示例:

 

 ?
 let value: unknown;
 ?
 value = true; // OK
 value = 42; // OK
 value = "Hello World"; // OK
 value = []; // OK
 value = {}; // OK
 value = Math.random; // OK
 value = null; // OK
 value = undefined; // OK
 value = new TypeError(); // OK
 value = Symbol("type"); // OK

 

value 變量的所有賦值都被認為是類型正確的。但是,當我們嘗試將類型為 unknown 的值賦值給其他類型的變量時會發(fā)生什么,?

 

 let value: unknown;let value1: unknown = value; // OKlet value2: any = value; // OKlet value3: boolean = value; // Errorlet value4: number = value; // Errorlet value5: string = value; // Errorlet value6: object = value; // Errorlet value7: any[] = value; // Errorlet value8: Function = value; // Error

 

unknown 類型只能被賦值給 any 類型和 unknown 類型本身,。直觀地說,這是有道理的:只有能夠保存任意類型值的容器才能保存 unknown 類型的值,。畢竟我們不知道變量 value 中存儲了什么類型的值。

 

現(xiàn)在讓我們看看當我們嘗試對類型為 unknown 的值執(zhí)行操作時會發(fā)生什么。以下是我們在之前 any 章節(jié)看過的相同操作:

 

 ?
 let value: unknown;
 ?
 let value1: unknown = value; // OK
 let value2: any = value; // OK
 let value3: boolean = value; // Error
 let value4: number = value; // Error
 let value5: string = value; // Error
 let value6: object = value; // Error
 let value7: any[] = value; // Error
 let value8: Function = value; // Error

 

value 變量類型設(shè)置為 unknown 后,這些操作都不再被認為是類型正確的。通過將 any 類型改變?yōu)?unknown 類型,,我們已將允許所有更改的默認設(shè)置,,更改為禁止任何更改。

 

2.8 Tuple 類型

 

眾所周知,數(shù)組一般由同種類型的值組成,但有時我們需要在單個變量中存儲不同類型的值,,這時候我們就可以使用元組,。在 JavaScript 中是沒有元組的,,元組是 TypeScript 中特有的類型,,其工作方式類似于數(shù)組。

 

元組可用于定義具有有限數(shù)量的未命名屬性的類型,。每個屬性都有一個關(guān)聯(lián)的類型,。使用元組時,必須提供每個屬性的值,。為了更直觀地理解元組的概念,,我們來看一個具體的例子:

 

 let tupleType: [string, boolean];tupleType = ["Semlinker", true];

 

在上面代碼中,我們定義了一個名為 tupleType 的變量,,它的類型是一個類型數(shù)組 [string, boolean],,然后我們按照正確的類型依次初始化 tupleType 變量。與數(shù)組一樣,,我們可以通過下標來訪問元組中的元素:

 

 console.log(tupleType[0]); // Semlinkerconsole.log(tupleType[1]); // true

 

在元組初始化的時候,,如果出現(xiàn)類型不匹配的話,比如:

 

 tupleType = [true, "Semlinker"];

 

此時,,TypeScript 編譯器會提示以下錯誤信息:

 

 [0]: Type 'true' is not assignable to type 'string'.[1]: Type 'string' is not assignable to type 'boolean'.

 

很明顯是因為類型不匹配導致的,。在元組初始化的時候,我們還必須提供每個屬性的值,,不然也會出現(xiàn)錯誤,,比如:

 

 tupleType = ["Semlinker"];

 

此時,TypeScript 編譯器會提示以下錯誤信息:

 

 Property '1' is missing in type '[string]' but required in type '[string, boolean]'.

 

2.9 Void 類型

 

某種程度上來說,,void 類型像是與 any 類型相反,,它表示沒有任何類型。當一個函數(shù)沒有返回值時,,你通常會見到其返回值類型是 void:

 

 // 聲明函數(shù)返回值為voidfunction warnUser(): void {  console.log("This is my warning message");}

 

以上代碼編譯生成的 ES5 代碼如下:

 

 "use strict";function warnUser() {  console.log("This is my warning message");}

 

需要注意的是,聲明一個 void 類型的變量沒有什么作用,,因為它的值只能為 undefinednull

 

 let unusable: void = undefined;

 

2.10 Null 和 Undefined 類型

 

TypeScript 里,,undefinednull 兩者有各自的類型分別為 undefinednull

 

 let u: undefined = undefined;let n: null = null;

 

默認情況下 nullundefined 是所有類型的子類型,。 就是說你可以把 nullundefined 賦值給 number 類型的變量,。然而,如果你指定了--strictNullChecks 標記,,nullundefined 只能賦值給 void 和它們各自的類型,。

 

2.11 Never 類型

 

never 類型表示的是那些永不存在的值的類型,。 例如,never 類型是那些總是會拋出異?;蚋揪筒粫蟹祷刂档暮瘮?shù)表達式或箭頭函數(shù)表達式的返回值類型,。

 

 ?
 // 返回never的函數(shù)必須存在無法達到的終點
 function error(message: string): never {
   throw new Error(message);
 }
 ?
 function infiniteLoop(): never {
   while (true) {}
 }

 

在 TypeScript 中,可以利用 never 類型的特性來實現(xiàn)全面性檢查,,具體示例如下:

 

 ?
 type Foo = string | number;
 ?
 function controlFlowAnalysisWithNever(foo: Foo) {
   if (typeof foo === "string") {
     // 這里 foo 被收窄為 string 類型
  } else if (typeof foo === "number") {
     // 這里 foo 被收窄為 number 類型
  } else {
     // foo 在這里是 never
     const check: never = foo;
  }
 }

 

注意在 else 分支里面,,我們把收窄為 never 的 foo 賦值給一個顯示聲明的 never 變量。如果一切邏輯正確,,那么這里應(yīng)該能夠編譯通過,。但是假如后來有一天你的同事修改了 Foo 的類型:

 

 type Foo = string | number | boolean;

 

然而他忘記同時修改 controlFlowAnalysisWithNever 方法中的控制流程,這時候 else 分支的 foo 類型會被收窄為 boolean 類型,,導致無法賦值給 never 類型,,這時就會產(chǎn)生一個編譯錯誤。通過這個方式,,我們可以確保

 

controlFlowAnalysisWithNever 方法總是窮盡了 Foo 的所有可能類型,。 通過這個示例,我們可以得出一個結(jié)論:使用 never 避免出現(xiàn)新增了聯(lián)合類型沒有對應(yīng)的實現(xiàn),,目的就是寫出類型絕對安全的代碼,。

 

三、TypeScript 斷言

 

有時候你會遇到這樣的情況,,你會比 TypeScript 更了解某個值的詳細信息,。通常這會發(fā)生在你清楚地知道一個實體具有比它現(xiàn)有類型更確切的類型。

 

通過類型斷言這種方式可以告訴編譯器,,“相信我,,我知道自己在干什么”。類型斷言好比其他語言里的類型轉(zhuǎn)換,,但是不進行特殊的數(shù)據(jù)檢查和解構(gòu),。它沒有運行時的影響,只是在編譯階段起作用,。

 

類型斷言有兩種形式:

 

3.1 “尖括號” 語法

 

 ?
 let someValue: any = "this is a string";
 let strLength: number = (<string>someValue).length;

 

3.2 as 語法

 

 ?
 let someValue: any = "this is a string";
 let strLength: number = (someValue as string).length;

 

四,、類型守衛(wèi)

 

A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— TypeScript 官方文檔

 

類型保護是可執(zhí)行運行時檢查的一種表達式,用于確保該類型在一定的范圍內(nèi),。換句話說,,類型保護可以保證一個字符串是一個字符串,盡管它的值也可以是一個數(shù)值,。類型保護與特性檢測并不是完全不同,,其主要思想是嘗試檢測屬性、方法或原型,,以確定如何處理值,。目前主要有四種的方式來實現(xiàn)類型保護:

 

4.1 in 關(guān)鍵字

 

 ?
 interface Admin {
   name: string;
   privileges: string[];
 }
 ?
 interface Employee {
   name: string;
   startDate: Date;
 }
 ?
 type UnknownEmployee = Employee | Admin;
 ?
 function printEmployeeInformation(emp: UnknownEmployee) {
   console.log("Name: " + emp.name);
   if ("privileges" in emp) {
     console.log("Privileges: " + emp.privileges);
  }
   if ("startDate" in emp) {
     console.log("Start Date: " + emp.startDate);
  }
 }

 

4.2 typeof 關(guān)鍵字

 

 ?
 function padLeft(value: string, padding: string | number) {
   if (typeof padding === "number") {
       return Array(padding + 1).join(" ") + value;
  }
   if (typeof padding === "string") {
       return padding + value;
  }
   throw new Error(`Expected string or number, got '${padding}'.`);
 }

 

typeof 類型保護只支持兩種形式:typeof v === "typename"typeof v !== typename,,"typename" 必須是 "number""string",, "boolean""symbol",。 但是 TypeScript 并不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護,。

 

4.3 instanceof 關(guān)鍵字

 

 ?
 interface Padder {
   getPaddingString(): string;
 }
 ?
 class SpaceRepeatingPadder implements Padder {
   constructor(private numSpaces: number) {}
   getPaddingString() {
     return Array(this.numSpaces + 1).join(" ");
  }
 }
 ?
 class StringPadder implements Padder {
   constructor(private value: string) {}
   getPaddingString() {
     return this.value;
  }
 }
 ?
 let padder: Padder = new SpaceRepeatingPadder(6);
 ?
 if (padder instanceof SpaceRepeatingPadder) {
   // padder的類型收窄為 'SpaceRepeatingPadder'
 }

 

4.4 自定義類型保護的類型謂詞

 

 ?
 function isNumber(x: any): x is number {
   return typeof x === "number";
 }
 ?
 function isString(x: any): x is string {
   return typeof x === "string";
 }

 

五,、聯(lián)合類型和類型別名

 

5.1 聯(lián)合類型

 

聯(lián)合類型通常與 nullundefined 一起使用:

 

 const sayHello = (name: string | undefined) => {  /* ... */};

 

例如,這里 name 的類型是 string | undefined 意味著可以將 stringundefined 的值傳遞給sayHello 函數(shù),。

 

 sayHello("Semlinker");sayHello(undefined);

 

通過這個示例,,你可以憑直覺知道類型 A 和類型 B 聯(lián)合后的類型是同時接受 A 和 B 值的類型。

 

5.2 可辨識聯(lián)合

 

TypeScript 可辨識聯(lián)合(Discriminated Unions)類型,,也稱為代數(shù)數(shù)據(jù)類型或標簽聯(lián)合類型,。它包含 3 個要點:可辨識、聯(lián)合類型和類型守衛(wèi),。

 

這種類型的本質(zhì)是結(jié)合聯(lián)合類型和字面量類型的一種類型保護方法,。如果一個類型是多個類型的聯(lián)合類型,且多個類型含有一個公共屬性,,那么就可以利用這個公共屬性,,來創(chuàng)建不同的類型保護區(qū)塊。

 

1.可辨識

 

可辨識要求聯(lián)合類型中的每個元素都含有一個單例類型屬性,,比如:

 

 ?
 enum CarTransmission {
   Automatic = 200,
   Manual = 300
 }
 ?
 interface Motorcycle {
   vType: "motorcycle"; // discriminant
   make: number; // year
 }
 ?
 interface Car {
   vType: "car"; // discriminant
   transmission: CarTransmission
 }
 ?
 interface Truck {
   vType: "truck"; // discriminant
   capacity: number; // in tons
 }

 

在上述代碼中,,我們分別定義了 MotorcycleCarTruck 三個接口,,在這些接口中都包含一個 vType 屬性,,該屬性被稱為可辨識的屬性,而其它的屬性只跟特性的接口相關(guān),。

 

2.聯(lián)合類型

 

基于前面定義了三個接口,,我們可以創(chuàng)建一個 Vehicle 聯(lián)合類型:

 

 type Vehicle = Motorcycle | Car | Truck;

 

現(xiàn)在我們就可以開始使用 Vehicle 聯(lián)合類型,對于 Vehicle 類型的變量,,它可以表示不同類型的車輛,。

 

3.類型守衛(wèi)

 

下面我們來定義一個 evaluatePrice 方法,該方法用于根據(jù)車輛的類型,、容量和評估因子來計算價格,,具體實現(xiàn)如下:

 

 ?
 const EVALUATION_FACTOR = Math.PI;
 function evaluatePrice(vehicle: Vehicle) {
   return vehicle.capacity * EVALUATION_FACTOR;
 }
 ?
 const myTruck: Truck = { vType: "truck", capacity: 9.5 };
 evaluatePrice(myTruck);

 

對于以上代碼,TypeScript 編譯器將會提示以下錯誤信息:

 

 ?
 Property 'capacity' does not exist on type 'Vehicle'.
 Property 'capacity' does not exist on type 'Motorcycle'.

 

原因是在 Motorcycle 接口中,,并不存在 capacity 屬性,而對于 Car 接口來說,,它也不存在 capacity 屬性,。那么,,現(xiàn)在我們應(yīng)該如何解決以上問題呢?這時,,我們可以使用類型守衛(wèi),。下面我們來重構(gòu)一下前面定義的 evaluatePrice 方法,重構(gòu)后的代碼如下:

 

 ?
 function evaluatePrice(vehicle: Vehicle) {
   switch(vehicle.vType) {
     case "car":
       return vehicle.transmission * EVALUATION_FACTOR;
     case "truck":
       return vehicle.capacity * EVALUATION_FACTOR;
     case "motorcycle":
       return vehicle.make * EVALUATION_FACTOR;
  }
 }

 

在以上代碼中,,我們使用 switchcase 運算符來實現(xiàn)類型守衛(wèi),,從而確保在 evaluatePrice 方法中,我們可以安全地訪問 vehicle 對象中的所包含的屬性,,來正確的計算該車輛類型所對應(yīng)的價格,。

 

5.3 類型別名

 

類型別名用來給一個類型起個新名字。

 

 type Message = string | string[];let greet = (message: Message) => {  // ...};

 

六,、交叉類型

 

TypeScript 交叉類型是將多個類型合并為一個類型,。 這讓我們可以把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性,。

 

 ?
 interface IPerson {
   id: string;
   age: number;
 }
 ?
 interface IWorker {
   companyId: string;
 }
 ?
 type IStaff = IPerson & IWorker;
 ?
 const staff: IStaff = {
   id: 'E1006',
   age: 33,
   companyId: 'EFT'
 };
 ?
 console.dir(staff)

 

在上面示例中,,我們首先為 IPerson 和 IWorker 類型定義了不同的成員,然后通過 & 運算符定義了 IStaff 交叉類型,,所以該類型同時擁有 IPerson 和 IWorker 這兩種類型的成員,。

 

七、TypeScript 函數(shù)

 

7.1 TypeScript 函數(shù)與 JavaScript 函數(shù)的區(qū)別

img

 

7.2 箭頭函數(shù)

 

1.常見語法

 

 ?
 myBooks.forEach(() => console.log('reading'));
 ?
 myBooks.forEach(title => console.log(title));
 ?
 myBooks.forEach((title, idx, arr) =>
   console.log(idx + '-' + title);
 );
 ?
 myBooks.forEach((title, idx, arr) => {
   console.log(idx + '-' + title);
 });

 

2.使用示例

 

 ?
 // 未使用箭頭函數(shù)
 function Book() {
   let self = this;
   self.publishDate = 2016;
   setInterval(function () {
     console.log(self.publishDate);
  }, 1000);
 }
 ?
 // 使用箭頭函數(shù)
 function Book() {
   this.publishDate = 2016;
   setInterval(() => {
     console.log(this.publishDate);
  }, 1000);
 }

 

7.3 參數(shù)類型和返回類型

 

 ?
 function createUserId(name: string, id: number): string {
   return name + id;
 }

 

7.4 函數(shù)類型

 

 ?
 let IdGenerator: (chars: string, nums: number) => string;
 ?
 function createUserId(name: string, id: number): string {
   return name + id;
 }
 ?
 IdGenerator = createUserId;

 

7.5 可選參數(shù)及默認參數(shù)

 

 ?
 // 可選參數(shù)
 function createUserId(name: string, id: number, age?: number): string {
   return name + id;
 }
 ?
 // 默認參數(shù)
 function createUserId(
   name: string = "Semlinker",
   id: number,
   age?: number
 ): string {
   return name + id;
 }

 

在聲明函數(shù)時,,可以通過 ? 號來定義可選參數(shù),,比如 age?: number 這種形式。在實際使用時,,需要注意的是可選參數(shù)要放在普通參數(shù)的后面,,不然會導致編譯錯誤。

 

7.6 剩余參數(shù)

 

 ?
 function push(array, ...items) {
   items.forEach(function (item) {
     array.push(item);
  });
 }
 ?
 let a = [];
 push(a, 1, 2, 3);

 

7.7 函數(shù)重載

 

函數(shù)重載或方法重載是使用相同名稱和不同參數(shù)數(shù)量或類型創(chuàng)建多個方法的一種能力,。要解決前面遇到的問題,,方法就是為同一個函數(shù)提供多個函數(shù)類型定義來進行函數(shù)重載,編譯器會根據(jù)這個列表去處理函數(shù)的調(diào)用,。

 

 ?
 function add(a: number, b: number): number;
 function add(a: string, b: string): string;
 function add(a: string, b: number): string;
 function add(a: number, b: string): string;
 function add(a: Combinable, b: Combinable) {
   if (typeof a === "string" || typeof b === "string") {
     return a.toString() + b.toString();
  }
   return a + b;
 }

 

在以上代碼中,,我們?yōu)?add 函數(shù)提供了多個函數(shù)類型定義,從而實現(xiàn)函數(shù)的重載,。之后,,可惡的錯誤消息又消失了,因為這時 result 變量的類型是 string 類型,。在 TypeScript 中除了可以重載普通函數(shù)之外,,我們還可以重載類中的成員方法。

 

方法重載是指在同一個類中方法同名,,參數(shù)不同(參數(shù)類型不同,、參數(shù)個數(shù)不同或參數(shù)個數(shù)相同時參數(shù)的先后順序不同),,調(diào)用時根據(jù)實參的形式,選擇與它匹配的方法執(zhí)行操作的一種技術(shù),。所以類中成員方法滿足重載的條件是:在同一個類中,,方法名相同且參數(shù)列表不同。下面我們來舉一個成員方法重載的例子:

 

 ?
 class Calculator {
   add(a: number, b: number): number;
   add(a: string, b: string): string;
   add(a: string, b: number): string;
   add(a: number, b: string): string;
   add(a: Combinable, b: Combinable) {
     if (typeof a === "string" || typeof b === "string") {
       return a.toString() + b.toString();
    }
     return a + b;
  }
 }
 ?
 const calculator = new Calculator();
 const result = calculator.add("Semlinker", " Kakuqo");

 

這里需要注意的是,,當 TypeScript 編譯器處理函數(shù)重載時,,它會查找重載列表,嘗試使用第一個重載定義,。 如果匹配的話就使用這個,。 因此,在定義重載的時候,,一定要把最精確的定義放在最前面,。另外在 Calculator 類中,add(a: Combinable, b: Combinable){ } 并不是重載列表的一部分,,因此對于 add 成員方法來說,,我們只定義了四個重載方法。

 

八,、TypeScript 數(shù)組

 

8.1 數(shù)組解構(gòu)

 

 ?
 let x: number; let y: number; let z: number;
 let five_array = [0,1,2,3,4];
 [x,y,z] = five_array;

 

8.2 數(shù)組展開運算符

 

 let two_array = [0, 1];
 let five_array = [...two_array, 2, 3, 4];

 

8.3 數(shù)組遍歷

 

 let colors: string[] = ["red", "green", "blue"];
 for (let i of colors) {  
  console.log(i);
 }

 

九,、TypeScript 對象

 

9.1 對象解構(gòu)

 

 ?
 let person = {
   name: "Semlinker",
   gender: "Male",
 };
 ?
 let { name, gender } = person;

 

9.2 對象展開運算符

 

 ?
 let person = {
   name: "Semlinker",
   gender: "Male",
   address: "Xiamen",
 };
 ?
 // 組裝對象
 let personWithAge = { ...person, age: 33 };
 ?
 // 獲取除了某些項外的其它項
 let { name, ...rest } = person;

 

十、TypeScript 接口

 

在面向?qū)ο笳Z言中,,接口是一個很重要的概念,,它是對行為的抽象,而具體如何行動需要由類去實現(xiàn),。

 

TypeScript 中的接口是一個非常靈活的概念,,除了可用于對類的一部分行為進行抽象以外,也常用于對「對象的形狀(Shape)」進行描述,。

 

10.1 對象的形狀

 

 ?
 interface Person {
   name: string;
   age: number;
 }
 ?
 let Semlinker: Person = {
   name: "Semlinker",
   age: 33,
 };

 

10.2 可選 | 只讀屬性

 

 ?
 interface Person {
   readonly name: string;
   age?: number;
 }

 

只讀屬性用于限制只能在對象剛剛創(chuàng)建的時候修改其值,。此外 TypeScript 還提供了 ReadonlyArray 類型,它與 Array 相似,,只是把所有可變方法去掉了,,因此可以確保數(shù)組創(chuàng)建后再也不能被修改。

 

 ?
 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!

 

十一,、TypeScript 類

 

11.1 類的屬性與方法

 

在面向?qū)ο笳Z言中,,類是一種面向?qū)ο笥嬎銠C編程語言的構(gòu)造,是創(chuàng)建對象的藍圖,,描述了所創(chuàng)建的對象共同的屬性和方法,。

 

在 TypeScript 中,我們可以通過 Class 關(guān)鍵字來定義一個類:

 

 ?
 class Greeter {
   // 靜態(tài)屬性
   static cname: string = "Greeter";
   // 成員屬性
   greeting: string;
 ?
   // 構(gòu)造函數(shù) - 執(zhí)行初始化操作
   constructor(message: string) {
     this.greeting = message;
  }
 ?
   // 靜態(tài)方法
   static getClassName() {
     return "Class name is Greeter";
  }
 ?
   // 成員方法
   greet() {
     return "Hello, " + this.greeting;
  }
 }
 ?
 let greeter = new Greeter("world");

 

那么成員屬性與靜態(tài)屬性,成員方法與靜態(tài)方法有什么區(qū)別呢,?這里無需過多解釋,,我們直接看一下以下編譯生成的 ES5 代碼:

 

 ?
 "use strict";
 var Greeter = /** @class */ (function () {
     // 構(gòu)造函數(shù) - 執(zhí)行初始化操作
     function Greeter(message) {
         this.greeting = message;
    }
     // 靜態(tài)方法
     Greeter.getClassName = function () {
         return "Class name is Greeter";
    };
     // 成員方法
     Greeter.prototype.greet = function () {
         return "Hello, " + this.greeting;
    };
     // 靜態(tài)屬性
     Greeter.cname = "Greeter";
     return Greeter;
 }());
 var greeter = new Greeter("world");

 

11.2 訪問器

 

在 TypeScript 中,,我們可以通過 gettersetter 方法來實現(xiàn)數(shù)據(jù)的封裝和有效性校驗,,防止出現(xiàn)異常數(shù)據(jù)。

 

 ?
 let passcode = "Hello TypeScript";
 ?
 class Employee {
   private _fullName: string;
 ?
   get fullName(): string {
     return this._fullName;
  }
 ?
   set fullName(newName: string) {
     if (passcode && passcode == "Hello TypeScript") {
       this._fullName = newName;
    } else {
       console.log("Error: Unauthorized update of employee!");
    }
  }
 }
 ?
 let employee = new Employee();
 employee.fullName = "Semlinker";
 if (employee.fullName) {
   console.log(employee.fullName);
 }

 

11.3 類的繼承

 

繼承 (Inheritance) 是一種聯(lián)結(jié)類與類的層次模型,。指的是一個類(稱為子類,、子接口)繼承另外的一個類(稱為父類、父接口)的功能,,并可以增加它自己的新功能的能力,,繼承是類與類或者接口與接口之間最常見的關(guān)系。

 

繼承是一種 is-a 關(guān)系:

 

img

 

在 TypeScript 中,,我們可以通過 extends 關(guān)鍵字來實現(xiàn)繼承:

 

 ?
 class Animal {
   name: string;
   
   constructor(theName: string) {
     this.name = theName;
  }
   
   move(distanceInMeters: number = 0) {
     console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
 }
 ?
 class Snake extends Animal {
   constructor(name: string) {
     super(name);
  }
   
   move(distanceInMeters = 5) {
     console.log("Slithering...");
     super.move(distanceInMeters);
  }
 }
 ?
 let sam = new Snake("Sammy the Python");
 sam.move();

 

11.4 ECMAScript 私有字段

 

在 TypeScript 3.8 版本就開始支持ECMAScript 私有字段,,使用方式如下:

 

 ?
 class Person {
   #name: string;
 ?
   constructor(name: string) {
     this.#name = name;
  }
 ?
   greet() {
     console.log(`Hello, my name is ${this.#name}!`);
  }
 }
 ?
 let semlinker = new Person("Semlinker");
 ?
 semlinker.#name;
 //     ~~~~~
 // Property '#name' is not accessible outside class 'Person'
 // because it has a private identifier.

 

與常規(guī)屬性(甚至使用 private 修飾符聲明的屬性)不同,,私有字段要牢記以下規(guī)則:

 

  • 私有字段以 # 字符開頭,,有時我們稱之為私有名稱;

  • 每個私有字段名稱都唯一地限定于其包含的類,;

  • 不能在私有字段上使用 TypeScript 可訪問性修飾符(如 public 或 private),;

  • 私有字段不能在包含的類之外訪問,,甚至不能被檢測到。

 

十二,、TypeScript 泛型

 

軟件工程中,,我們不僅要創(chuàng)建一致的定義良好的 API,同時也要考慮可重用性,。 組件不僅能夠支持當前的數(shù)據(jù)類型,,同時也能支持未來的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時為你提供了十分靈活的功能,。

 

在像 C# 和 Java 這樣的語言中,,可以使用泛型來創(chuàng)建可重用的組件,一個組件可以支持多種類型的數(shù)據(jù),。 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件,。

 

設(shè)計泛型的關(guān)鍵目的是在成員之間提供有意義的約束,這些成員可以是:類的實例成員,、類的方法,、函數(shù)參數(shù)和函數(shù)返回值。

 

泛型(Generics)是允許同一個函數(shù)接受不同類型參數(shù)的一種模板,。相比于使用 any 類型,,使用泛型來創(chuàng)建可復用的組件要更好,因為泛型會保留參數(shù)類型。

 

12.1 泛型接口

 

 interface GenericIdentityFn<T> {  (arg: T): T;}

 

12.2 泛型類

 

 ?
 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;
 };

 

12.3 泛型變量

 

對剛接觸 TypeScript 泛型的小伙伴來說,,看到 T 和 E,,還有 K 和 V 這些泛型變量時,估計會一臉懵逼,。其實這些大寫字母并沒有什么本質(zhì)的區(qū)別,,只不過是一個約定好的規(guī)范而已。也就是說使用大寫字母 A-Z 定義的類型變量都屬于泛型,,把 T 換成 A,,也是一樣的。下面我們介紹一下一些常見泛型變量代表的意思:

 

  • T(Type):表示一個 TypeScript 類型

  • K(Key):表示對象中的鍵類型

  • V(Value):表示對象中的值類型

  • E(Element):表示元素類型

 

12.4 泛型工具類型

 

為了方便開發(fā)者 TypeScript 內(nèi)置了一些常用的工具類型,,比如 Partial,、Required、Readonly,、Record 和 ReturnType 等,。出于篇幅考慮,這里我們只簡單介紹 Partial 工具類型,。不過在具體介紹之前,,我們得先介紹一些相關(guān)的基礎(chǔ)知識,方便讀者自行學習其它的工具類型,。

 

1.typeof

 

在 TypeScript 中,,typeof 操作符可以用來獲取一個變量聲明或?qū)ο蟮念愋汀?/span>

 

 ?
 interface Person {
   name: string;
   age: number;
 }
 ?
 const sem: Person = { name: 'semlinker', age: 30 };
 type Sem= typeof sem; // -> Person
 ?
 function toArray(x: number): Array<number> {
   return [x];
 }
 ?
 type Func = typeof toArray; // -> (x: number) => number[]

 

2.keyof

 

keyof 操作符可以用來一個對象中的所有 key 值:

 

 ?
 interface Person {
     name: string;
     age: number;
 }
 ?
 type K1 = keyof Person; // "name" | "age"
 type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
 type K3 = keyof { [x: string]: Person };  // string | number

 

3.in

 

in 用來遍歷枚舉類型:

 

 ?
 type Keys = "a" | "b" | "c"
 ?
 type Obj = {
  [p in Keys]: any
 } // -> { a: any, b: any, c: any }

 

4.infer

 

在條件類型語句中,可以用 infer 聲明一個類型變量并且對它進行使用,。

 

 ?
 type ReturnType<T> = T extends (
   ...args: any[]
 ) => infer R ? R : any;

 

以上代碼中 infer R 就是聲明一個變量來承載傳入函數(shù)簽名的返回值類型,,簡單說就是用它取到函數(shù)返回值的類型方便之后使用。

 

5.extends

 

有時候我們定義的泛型不想過于靈活或者說想繼承某些類等,,可以通過 extends 關(guān)鍵字添加泛型約束,。

 

 ?
 interface ILengthwise {
   length: number;
 }
 ?
 function loggingIdentity<T extends ILengthwise>(arg: T): T {
   console.log(arg.length);
   return arg;
 }

 

現(xiàn)在這個泛型函數(shù)被定義了約束,因此它不再是適用于任意類型:

 

 loggingIdentity(3);  // Error, number doesn't have a .length property

 

這時我們需要傳入符合約束類型的值,,必須包含必須的屬性:

 

 loggingIdentity({length: 10, value: 3});

 

6.Partial

 

Partial 的作用就是將某個類型里的屬性全部變?yōu)榭蛇x項 ?,。

 

定義:

 

 ?
 /**
  * node_modules/typescript/lib/lib.es5.d.ts
  * Make all properties in T optional
  */
 type Partial<T> = {
  [P in keyof T]?: T[P];
 };

 

在以上代碼中,首先通過 keyof T 拿到 T 的所有屬性名,,然后使用 in 進行遍歷,,將值賦給 P,最后通過 T[P] 取得相應(yīng)的屬性值,。中間的 ? 號,,用于將所有屬性變?yōu)榭蛇x。

 

示例:

 

 ?
 interface Todo {
   title: string;
   description: string;
 }
 ?
 function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
   return { ...todo, ...fieldsToUpdate };
 }
 ?
 const todo1 = {
   title: "organize desk",
   description: "clear clutter",
 };
 ?
 const todo2 = updateTodo(todo1, {
   description: "throw out trash",
 });

 

在上面的 updateTodo 方法中,,我們利用 Partial 工具類型,,定義 fieldsToUpdate 的類型為 Partial,,即:

 

 {   title?: string | undefined;   description?: string | undefined;}

 

十三、TypeScript 裝飾器

 

13.1 裝飾器是什么

 

  • 它是一個表達式

  • 該表達式被執(zhí)行后,,返回一個函數(shù)

  • 函數(shù)的入?yún)⒎謩e為 target,、name 和 descriptor

  • 執(zhí)行該函數(shù)后,可能返回 descriptor 對象,,用于配置 target 對象

 

13.2 裝飾器的分類

 

  • 類裝飾器(Class decorators)

  • 屬性裝飾器(Property decorators)

  • 方法裝飾器(Method decorators)

  • 參數(shù)裝飾器(Parameter decorators)

 

13.3 類裝飾器

 

類裝飾器聲明:

 

 ?
 declare type ClassDecorator = <TFunction extends Function>(
   target: TFunction
 ) => TFunction | void;

 

類裝飾器顧名思義,,就是用來裝飾類的。它接收一個參數(shù):

 

  • target: TFunction - 被裝飾的類

 

看完第一眼后,,是不是感覺都不好了,。沒事,我們馬上來個例子:

 

 ?
 function Greeter(target: Function): void {
   target.prototype.greet = function (): void {
     console.log("Hello Semlinker!");
  };
 }
 ?
 @Greeter
 class Greeting {
   constructor() {
     // 內(nèi)部實現(xiàn)
  }
 }
 ?
 let myGreeting = new Greeting();
 myGreeting.greet(); // console output: 'Hello Semlinker!';

 

上面的例子中,,我們定義了 Greeter 類裝飾器,同時我們使用了 @Greeter 語法糖,,來使用裝飾器,。

 

友情提示:讀者可以直接復制上面的代碼,在 TypeScript Playground 中運行查看結(jié)果,。

 

有的讀者可能想問,,例子中總是輸出 Hello Semlinker! ,能自定義輸出的問候語么 ,?這個問題很好,,答案是可以的。

 

具體實現(xiàn)如下:

 

 ?
 function Greeter(greeting: string) {
   return function (target: Function) {
     target.prototype.greet = function (): void {
       console.log(greeting);
    };
  };
 }
 ?
 @Greeter("Hello TS!")
 class Greeting {
   constructor() {
     // 內(nèi)部實現(xiàn)
  }
 }
 ?
 let myGreeting = new Greeting();
 myGreeting.greet(); // console output: 'Hello TS!';

 

13.4 屬性裝飾器

 

屬性裝飾器聲明:

 

 ?
 declare type PropertyDecorator = (target:Object,
   propertyKey: string | symbol ) => void;

 

屬性裝飾器顧名思義,,用來裝飾類的屬性,。它接收兩個參數(shù):

 

  • 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("semlinker");
 p1.name = "kakuqo";

 

以上代碼我們定義了一個 logProperty 函數(shù),,來跟蹤用戶對屬性的操作,,當代碼成功運行后,在控制臺會輸出以下結(jié)果:

 

 Set: name => semlinkerSet: name => kakuqo

 

13.5 方法裝飾器

 

方法裝飾器聲明:

 

 ?
 declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
   descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

 

方法裝飾器顧名思義,,用來裝飾類的方法,。它接收三個參數(shù):

 

  • target: Object - 被裝飾的類

  • propertyKey: string | symbol - 方法名

  • descriptor: TypePropertyDescript - 屬性描述符

 

廢話不多說,直接上例子:

 

 ?
 function LogOutput(tarage: Function, key: string, descriptor: any) {
   let originalMethod = descriptor.value;
   let newMethod = function(...args: any[]): any {
     let result: any = originalMethod.apply(this, args);
     if(!this.loggedOutput) {
       this.loggedOutput = new Array<any>();
    }
     this.loggedOutput.push({
       method: key,
       parameters: args,
       output: result,
       timestamp: new Date()
    });
     return result;
  };
   descriptor.value = newMethod;
 }
 ?
 class Calculator {
   @LogOutput
   double (num: number): number {
     return num * 2;
  }
 }
 ?
 let calc = new Calculator();
 calc.double(11);
 // console ouput: [{method: "double", output: 22, ...}]
 console.log(calc.loggedOutput);

 

下面我們來介紹一下參數(shù)裝飾器,。

 

13.6 參數(shù)裝飾器

 

參數(shù)裝飾器聲明:

 

 ?
 declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,
   parameterIndex: number ) => void

 

參數(shù)裝飾器顧名思義,,是用來裝飾函數(shù)參數(shù),它接收三個參數(shù):

 

  • target: Object - 被裝飾的類

  • propertyKey: string | symbol - 方法名

  • parameterIndex: number - 方法中參數(shù)的索引值

 

 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;   }}// console output: The parameter in position 0 // at Greeter has been decorated

 

介紹完 TypeScript 入門相關(guān)的基礎(chǔ)知識,,猜測很多剛?cè)腴T的小伙伴已有 “從入門到放棄” 的想法,,最后我們來簡單介紹一下編譯上下文。

 

十四,、編譯上下文

 

14.1 tsconfig.json 的作用

 

  • 用于標識 TypeScript 項目的根路徑,;

  • 用于配置 TypeScript 編譯器;

  • 用于指定編譯的文件。

 

14.2 tsconfig.json 重要字段

 

  • files - 設(shè)置要編譯的文件的名稱,;

  • include - 設(shè)置需要進行編譯的文件,,支持路徑模式匹配;

  • exclude - 設(shè)置無需進行編譯的文件,,支持路徑模式匹配,;

  • compilerOptions - 設(shè)置與編譯流程相關(guān)的選項。

 

14.3 compilerOptions 選項

 

compilerOptions 支持很多選項,,常見的有 baseUrl,、 targetbaseUrl,、 moduleResolutionlib 等,。

 

compilerOptions 每個選項的詳細說明如下:

 

 ?
 {
   "compilerOptions": {
 ?
     /* 基本選項 */
     "target": "es5",                       // 指定 ECMAScript 目標版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
     "module": "commonjs",                  // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
     "lib": [],                             // 指定要包含在編譯中的庫文件
     "allowJs": true,                       // 允許編譯 javascript 文件
     "checkJs": true,                       // 報告 javascript 文件中的錯誤
     "jsx": "preserve",                     // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
     "declaration": true,                   // 生成相應(yīng)的 '.d.ts' 文件
     "sourceMap": true,                     // 生成相應(yīng)的 '.map' 文件
     "outFile": "./",                       // 將輸出文件合并為一個文件
     "outDir": "./",                        // 指定輸出目錄
     "rootDir": "./",                       // 用來控制輸出目錄結(jié)構(gòu) --outDir.
     "removeComments": true,                // 刪除編譯后的所有的注釋
     "noEmit": true,                        // 不生成輸出文件
     "importHelpers": true,                 // 從 tslib 導入輔助工具函數(shù)
     "isolatedModules": true,               // 將每個文件做為單獨的模塊 (與 'ts.transpileModule' 類似).
 ?
     /* 嚴格的類型檢查選項 */
     "strict": true,                        // 啟用所有嚴格類型檢查選項
     "noImplicitAny": true,                 // 在表達式和聲明上有隱含的 any類型時報錯
     "strictNullChecks": true,              // 啟用嚴格的 null 檢查
     "noImplicitThis": true,                // 當 this 表達式值為 any 類型的時候,生成一個錯誤
     "alwaysStrict": true,                  // 以嚴格模式檢查每個模塊,,并在每個文件里加入 'use strict'
 ?
     /* 額外的檢查 */
     "noUnusedLocals": true,                // 有未使用的變量時,,拋出錯誤
     "noUnusedParameters": true,            // 有未使用的參數(shù)時,拋出錯誤
     "noImplicitReturns": true,             // 并不是所有函數(shù)里的代碼都有返回值時,,拋出錯誤
     "noFallthroughCasesInSwitch": true,    // 報告 switch 語句的 fallthrough 錯誤,。(即,不允許 switch 的 case 語句貫穿)
 ?
     /* 模塊解析選項 */
     "moduleResolution": "node",            // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
     "baseUrl": "./",                       // 用于解析非相對模塊名稱的基目錄
     "paths": {},                           // 模塊名到基于 baseUrl 的路徑映射的列表
     "rootDirs": [],                        // 根文件夾列表,,其組合內(nèi)容表示項目運行時的結(jié)構(gòu)內(nèi)容
     "typeRoots": [],                       // 包含類型聲明的文件列表
     "types": [],                           // 需要包含的類型聲明文件名列表
     "allowSyntheticDefaultImports": true,  // 允許從沒有設(shè)置默認導出的模塊中默認導入,。
 ?
     /* Source Map Options */
     "sourceRoot": "./",                    // 指定調(diào)試器應(yīng)該找到 TypeScript 文件而不是源文件的位置
     "mapRoot": "./",                       // 指定調(diào)試器應(yīng)該找到映射文件而不是生成文件的位置
     "inlineSourceMap": true,               // 生成單個 soucemaps 文件,而不是將 sourcemaps 生成不同的文件
     "inlineSources": true,                 // 將代碼與 sourcemaps 生成到一個文件中,,要求同時設(shè)置了 --inlineSourceMap 或 --sourceMap 屬性
 ?
     /* 其他選項 */
     "experimentalDecorators": true,        // 啟用裝飾器
     "emitDecoratorMetadata": true          // 為裝飾器提供元數(shù)據(jù)的支持
  }
 }

總結(jié)

對了,,小編為大家準備了一套2020最新的web前端資料,需要點擊下方鏈接獲取方式

學習前端,,你掌握這些,。二線也能輕松拿8K以上

 

 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多