溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

TypeScript中有哪些奇怪的符號(hào)

發(fā)布時(shí)間:2021-10-28 15:40:32 來(lái)源:億速云 閱讀:137 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“TypeScript中有哪些奇怪的符號(hào)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“TypeScript中有哪些奇怪的符號(hào)”吧!

一、! 非空斷言操作符

在上下文中當(dāng)類型檢查器無(wú)法斷定類型時(shí),一個(gè)新的后綴表達(dá)式操作符 ! 可以用于斷言操作對(duì)象是非 null 和非 undefined 類型。具體而言,x!  將從 x 值域中排除 null 和 undefined 。

那么非空斷言操作符到底有什么用呢?下面我們先來(lái)看一下非空斷言操作符的一些使用場(chǎng)景。

1.1 忽略 undefined 和 null 類型

function myFunc(maybeString: string | undefined | null) {   // Type 'string | null | undefined' is not assignable to type 'string'.   // Type 'undefined' is not assignable to type 'string'.    const onlyString: string = maybeString; // Error   const ignoreUndefinedAndNull: string = maybeString!; // Ok }

1.2 調(diào)用函數(shù)時(shí)忽略 undefined 類型

type NumGenerator = () => number;  function myFunc(numGenerator: NumGenerator | undefined) {   // Object is possibly 'undefined'.(2532)   // Cannot invoke an object which is possibly 'undefined'.(2722)   const num1 = numGenerator(); // Error   const num2 = numGenerator!(); //OK }

因?yàn)?! 非空斷言操作符會(huì)從編譯生成的 JavaScript 代碼中移除,所以在實(shí)際使用的過(guò)程中,要特別注意。比如下面這個(gè)例子:

const a: number | undefined = undefined; const b: number = a!; console.log(b);

以上 TS 代碼會(huì)編譯生成以下 ES5 代碼:

"use strict"; const a = undefined; const b = a; console.log(b);

雖然在 TS 代碼中,我們使用了非空斷言,使得 const b: number = a!; 語(yǔ)句可以通過(guò) TypeScript  類型檢查器的檢查。但在生成的 ES5 代碼中,! 非空斷言操作符被移除了,所以在瀏覽器中執(zhí)行以上代碼,在控制臺(tái)會(huì)輸出 undefined。

? 繼續(xù)閱讀:介紹了 ?. 和 ?? 運(yùn)算符,再來(lái)個(gè) ! 非空斷言操作符

二、?. 運(yùn)算符

TypeScript 3.7 實(shí)現(xiàn)了呼聲最高的 ECMAScript 功能之一:可選鏈(Optional  Chaining)。有了可選鏈后,我們編寫代碼時(shí)如果遇到 null 或 undefined 就可以立即停止某些表達(dá)式的運(yùn)行??蛇x鏈的核心是新的 ?.  運(yùn)算符,它支持以下語(yǔ)法:

obj?.prop obj?.[expr] arr?.[index] func?.(args)

這里我們來(lái)舉一個(gè)可選的屬性訪問(wèn)的例子:

const val = a?.b;

為了更好的理解可選鏈,我們來(lái)看一下該 const val = a?.b 語(yǔ)句編譯生成的 ES5 代碼:

var val = a === null || a === void 0 ? void 0 : a.b;

上述的代碼會(huì)自動(dòng)檢查對(duì)象 a 是否為 null 或 undefined,如果是的話就立即返回  undefined,這樣就可以立即停止某些表達(dá)式的運(yùn)行。你可能已經(jīng)想到可以使用 ?. 來(lái)替代很多使用 && 執(zhí)行空檢查的代碼:

if(a && a.b) { }   if(a?.b){ } /** * if(a?.b){ } 編譯后的ES5代碼 *  * if( *  a === null || a === void 0  *  ? void 0 : a.b) { * } */

但需要注意的是,?. 與 && 運(yùn)算符行為略有不同,&& 專門用于檢測(cè) falsy 值,比如空字符串、0、NaN、null  和 false 等。而 ?. 只會(huì)驗(yàn)證對(duì)象是否為 null 或 undefined,對(duì)于 0 或空字符串來(lái)說(shuō),并不會(huì)出現(xiàn) “短路”。

2.1 可選元素訪問(wèn)

可選鏈除了支持可選屬性的訪問(wèn)之外,它還支持可選元素的訪問(wèn),它的行為類似于可選屬性的訪問(wèn),只是可選元素的訪問(wèn)允許我們?cè)L問(wèn)非標(biāo)識(shí)符的屬性,比如任意字符串、數(shù)字索引和  Symbol:

function tryGetArrayElement<T>(arr?: T[], index: number = 0) {   return arr?.[index]; }

以上代碼經(jīng)過(guò)編譯后會(huì)生成以下 ES5 代碼:

"use strict"; function tryGetArrayElement(arr, index) {     if (index === void 0) { index = 0; }     return arr === null || arr === void 0 ? void 0 : arr[index]; }

通過(guò)觀察生成的 ES5 代碼,很明顯在 tryGetArrayElement 方法中會(huì)自動(dòng)檢測(cè)輸入?yún)?shù) arr 的值是否為 null 或  undefined,從而保證了我們代碼的健壯性。

2.2 可選鏈與函數(shù)調(diào)用

當(dāng)嘗試調(diào)用一個(gè)可能不存在的方法時(shí)也可以使用可選鏈。在實(shí)際開(kāi)發(fā)過(guò)程中,這是很有用的。系統(tǒng)中某個(gè)方法不可用,有可能是由于版本不一致或者用戶設(shè)備兼容性問(wèn)題導(dǎo)致的。函數(shù)調(diào)用時(shí)如果被調(diào)用的方法不存在,使用可選鏈可以使表達(dá)式自動(dòng)返回  undefined 而不是拋出一個(gè)異常。

可選調(diào)用使用起來(lái)也很簡(jiǎn)單,比如:

let result = obj.customMethod?.();

該 TypeScript 代碼編譯生成的 ES5 代碼如下:

var result = (_a = obj.customMethod) === null   || _a === void 0 ? void 0 : _a.call(obj);

另外在使用可選調(diào)用的時(shí)候,我們要注意以下兩個(gè)注意事項(xiàng):

  • 如果存在一個(gè)屬性名且該屬性名對(duì)應(yīng)的值不是函數(shù)類型,使用 ?. 仍然會(huì)產(chǎn)生一個(gè) TypeError 異常。

  • 可選鏈的運(yùn)算行為被局限在屬性的訪問(wèn)、調(diào)用以及元素的訪問(wèn) &mdash;&mdash; 它不會(huì)沿伸到后續(xù)的表達(dá)式中,也就是說(shuō)可選調(diào)用不會(huì)阻止 a?.b / someMethod()  表達(dá)式中的除法運(yùn)算或 someMethod 的方法調(diào)用。

? 繼續(xù)閱讀:遇到訪問(wèn)對(duì)象深層次屬性怎么辦?可選鏈了解一下!

三、?? 空值合并運(yùn)算符

在 TypeScript 3.7 版本中除了引入了前面介紹的可選鏈 ?. 之外,也引入了一個(gè)新的邏輯運(yùn)算符 &mdash;&mdash; 空值合并運(yùn)算符 ??。當(dāng)左側(cè)操作數(shù)為  null 或 undefined 時(shí),其返回右側(cè)的操作數(shù),否則返回左側(cè)的操作數(shù)。

與邏輯或 || 運(yùn)算符不同,邏輯或會(huì)在左操作數(shù)為 falsy 值時(shí)返回右側(cè)操作數(shù)。也就是說(shuō),如果你使用 ||  來(lái)為某些變量設(shè)置默認(rèn)的值時(shí),你可能會(huì)遇到意料之外的行為。比如為 falsy 值(''、NaN 或 0)時(shí)。

這里來(lái)看一個(gè)具體的例子:

const foo = null ?? 'default string'; console.log(foo); // 輸出:"default string"  const baz = 0 ?? 42; console.log(baz); // 輸出:0

以上 TS 代碼經(jīng)過(guò)編譯后,會(huì)生成以下 ES5 代碼:

"use strict"; var _a, _b; var foo = (_a = null) !== null && _a !== void 0 ? _a : 'default string'; console.log(foo); // 輸出:"default string"  var baz = (_b = 0) !== null && _b !== void 0 ? _b : 42; console.log(baz); // 輸出:0

通過(guò)觀察以上代碼,我們更加直觀的了解到,空值合并運(yùn)算符是如何解決前面 ||  運(yùn)算符存在的潛在問(wèn)題。下面我們來(lái)介紹空值合并運(yùn)算符的特性和使用時(shí)的一些注意事項(xiàng)。

3.1 短路

當(dāng)空值合并運(yùn)算符的左表達(dá)式不為 null 或 undefined 時(shí),不會(huì)對(duì)右表達(dá)式進(jìn)行求值。

function A() { console.log('A was called'); return undefined;} function B() { console.log('B was called'); return false;} function C() { console.log('C was called'); return "foo";}  console.log(A() ?? C()); console.log(B() ?? C());

上述代碼運(yùn)行后,控制臺(tái)會(huì)輸出以下結(jié)果:

A was called  C was called  foo  B was called  false

3.2 不能與 && 或 || 操作符共用

若空值合并運(yùn)算符 ?? 直接與 AND(&&)和 OR(||)操作符組合使用 ?? 是不行的。這種情況下會(huì)拋出  SyntaxError。

// '||' and '??' operations cannot be mixed without parentheses.(5076) null || undefined ?? "foo"; // raises a SyntaxError  // '&&' and '??' operations cannot be mixed without parentheses.(5076) true && undefined ?? "foo"; // raises a SyntaxError

但當(dāng)使用括號(hào)來(lái)顯式表明優(yōu)先級(jí)時(shí)是可行的,比如:

(null || undefined ) ?? "foo"; // 返回 "foo"

3.3 與可選鏈操作符 ?. 的關(guān)系

空值合并運(yùn)算符針對(duì) undefined 與 null 這兩個(gè)值,可選鏈?zhǔn)讲僮鞣??. 也是如此??蛇x鏈?zhǔn)讲僮鞣瑢?duì)于訪問(wèn)屬性可能為 undefined 與  null 的對(duì)象時(shí)非常有用。

interface Customer {   name: string;   city?: string; }  let customer: Customer = {   name: "Semlinker" };  let customerCity = customer?.city ?? "Unknown city"; console.log(customerCity); // 輸出:Unknown city

前面我們已經(jīng)介紹了空值合并運(yùn)算符的應(yīng)用場(chǎng)景和使用時(shí)的一些注意事項(xiàng),該運(yùn)算符不僅可以在 TypeScript 3.7 以上版本中使用。當(dāng)然你也可以在  JavaScript 的環(huán)境中使用它,但你需要借助 Babel,在 Babel 7.8.0 版本也開(kāi)始支持空值合并運(yùn)算符。

四、?: 可選屬性

在面向?qū)ο笳Z(yǔ)言中,接口是一個(gè)很重要的概念,它是對(duì)行為的抽象,而具體如何行動(dòng)需要由類去實(shí)現(xiàn)。TypeScript  中的接口是一個(gè)非常靈活的概念,除了可用于對(duì)類的一部分行為進(jìn)行抽象以外,也常用于對(duì)「對(duì)象的形狀(Shape)」進(jìn)行描述。

在 TypeScript 中使用 interface 關(guān)鍵字就可以聲明一個(gè)接口:

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

在以上代碼中,我們聲明了 Person 接口,它包含了兩個(gè)必填的屬性 name 和 age。在初始化 Person  類型變量時(shí),如果缺少某個(gè)屬性,TypeScript 編譯器就會(huì)提示相應(yīng)的錯(cuò)誤信息,比如:

// Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.(2741) let lolo: Person  = { // Error   name: "lolo"   }

為了解決上述的問(wèn)題,我們可以把某個(gè)屬性聲明為可選的:

interface Person {   name: string;   age?: number; }  let lolo: Person  = {   name: "lolo"   }

4.1 工具類型

4.1.1 Partial

在實(shí)際項(xiàng)目開(kāi)發(fā)過(guò)程中,為了提高代碼復(fù)用率,我們可以利用 TypeScript 內(nèi)置的工具類型 Partial來(lái)快速把某個(gè)接口類型中定義的屬性變成可選的:

interface PullDownRefreshConfig {   threshold: number;   stop: number; }  /**  * type PullDownRefreshOptions = {  *   threshold?: number | undefined;  *   stop?: number | undefined;  * }  */  type PullDownRefreshOptions = Partial<PullDownRefreshConfig>

是不是覺(jué)得 Partial很方便,下面讓我們來(lái)看一下它是如何實(shí)現(xiàn)的:

/**  * Make all properties in T optional  */ type Partial<T> = {   [P in keyof T]?: T[P]; };

4.1.2 Required

既然可以快速地把某個(gè)接口中定義的屬性全部聲明為可選,那能不能把所有的可選的屬性變成必選的呢?答案是可以的,針對(duì)這個(gè)需求,我們可以使用  Required工具類型,具體的使用方式如下:

interface PullDownRefreshConfig {   threshold: number;   stop: number; }  type PullDownRefreshOptions = Partial<PullDownRefreshConfig>  /**  * type PullDownRefresh = {  *   threshold: number;  *   stop: number;  * }  */ type PullDownRefresh = Required<Partial<PullDownRefreshConfig>>

同樣,我們來(lái)看一下 Required工具類型是如何實(shí)現(xiàn)的:

/**  * Make all properties in T required  */ type Required<T> = {   [P in keyof T]-?: T[P]; };

原來(lái)在 Required工具類型內(nèi)部,通過(guò) -? 移除了可選屬性中的 ?,使得屬性從可選變?yōu)楸剡x的。

? 繼續(xù)閱讀:掌握 TS 這些工具類型,讓你開(kāi)發(fā)事半功倍

五、& 運(yùn)算符

在 TypeScript 中交叉類型是將多個(gè)類型合并為一個(gè)類型。通過(guò) &  運(yùn)算符可以將現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。

type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; };  let point: Point = {   x: 1,   y: 1 }

在上面代碼中我們先定義了 PartialPointX 類型,接著使用 & 運(yùn)算符創(chuàng)建一個(gè)新的 Point 類型,表示一個(gè)含有 x 和 y  坐標(biāo)的點(diǎn),然后定義了一個(gè) Point 類型的變量并初始化。

5.1 同名基礎(chǔ)類型屬性的合并

那么現(xiàn)在問(wèn)題來(lái)了,假設(shè)在合并多個(gè)類型的過(guò)程中,剛好出現(xiàn)某些類型存在相同的成員,但對(duì)應(yīng)的類型又不一致,比如:

interface X {   c: string;   d: string; }  interface Y {   c: number;   e: string }  type XY = X & Y; type YX = Y & X;  let p: XY; let q: YX;

在上面的代碼中,接口 X 和接口 Y 都含有一個(gè)相同的成員 c,但它們的類型不一致。對(duì)于這種情況,此時(shí) XY 類型或 YX 類型中成員 c  的類型是不是可以是 string 或 number類型呢?比如下面的例子:

p = { c: 6, d: "d", e: "e" };

TypeScript中有哪些奇怪的符號(hào)

q = { c: "c", d: "d", e: "e" };

TypeScript中有哪些奇怪的符號(hào)

為什么接口 X 和接口 Y 混入后,成員 c 的類型會(huì)變成 never 呢?這是因?yàn)榛烊牒蟪蓡T c 的類型為 string &  number,即成員 c 的類型既可以是 string 類型又可以是number 類型。很明顯這種類型是不存在的,所以混入后成員 c 的類型為  never。

5.2 同名非基礎(chǔ)類型屬性的合并

在上面示例中,剛好接口 X 和接口 Y 中內(nèi)部成員 c  的類型都是基本數(shù)據(jù)類型,那么如果是非基本數(shù)據(jù)類型的話,又會(huì)是什么情形。我們來(lái)看個(gè)具體的例子:

interface D { d: boolean; } interface E { e: string; } interface F { f: number; }  interface A { x: D; } interface B { x: E; } interface C { x: F; }  type ABC = A & B & C;  let abc: ABC = {   x: {     d: true,     e: 'semlinker',     f: 666   } };  console.log('abc:', abc);

以上代碼成功運(yùn)行后,控制臺(tái)會(huì)輸出以下結(jié)果:

TypeScript中有哪些奇怪的符號(hào)

由上圖可知,在混入多個(gè)類型時(shí),若存在相同的成員,且成員類型為非基本數(shù)據(jù)類型,那么是可以成功合并。

六、| 分隔符

在 TypeScript 中聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種,聯(lián)合類型使用 | 分隔每個(gè)類型。聯(lián)合類型通常與 null 或  undefined 一起使用:

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

以上示例中 name 的類型是 string | undefined 意味著可以將 string 或undefined 的值傳遞給 sayHello  函數(shù)。

sayHello("semlinker"); sayHello(undefined);

此外,對(duì)于聯(lián)合類型來(lái)說(shuō),你可能會(huì)遇到以下的用法:

let num: 1 | 2 = 1; type EventNames = 'click' | 'scroll' | 'mousemove';

示例中的 1、2 或 'click' 被稱為字面量類型,用來(lái)約束取值只能是某幾個(gè)值中的一個(gè)。

6.1 類型保護(hù)

當(dāng)使用聯(lián)合類型時(shí),我們必須盡量把當(dāng)前值的類型收窄為當(dāng)前值的實(shí)際類型,而類型保護(hù)就是實(shí)現(xiàn)類型收窄的一種手段。

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

目前主要有四種的方式來(lái)實(shí)現(xiàn)類型保護(hù):

6.1.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);   } }

6.1.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 類型保護(hù)只支持兩種形式:typeof v === "typename" 和 typeof v !== typename,"typename"  必須是 "number", "string", "boolean" 或 "symbol"。但是 TypeScript  并不會(huì)阻止你與其它字符串比較,語(yǔ)言不會(huì)把那些表達(dá)式識(shí)別為類型保護(hù)。

6.1.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' }

6.1.4 自定義類型保護(hù)的類型謂詞(type predicate)

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

? 繼續(xù)閱讀:讀懂 TS 中聯(lián)合類型和交叉類型的含義

七、_ 數(shù)字分隔符

TypeScript 2.7 帶來(lái)了對(duì)數(shù)字分隔符的支持,正如數(shù)值分隔符 ECMAScript  提案中所概述的那樣。對(duì)于一個(gè)數(shù)字字面量,你現(xiàn)在可以通過(guò)把一個(gè)下劃線作為它們之間的分隔符來(lái)分組數(shù)字:

const inhabitantsOfMunich = 1_464_301; const distanceEarthSunInKm = 149_600_000; const fileSystemPermission = 0b111_111_000; const bytes = 0b1111_10101011_11110000_00001101;

分隔符不會(huì)改變數(shù)值字面量的值,但邏輯分組使人們更容易一眼就能讀懂?dāng)?shù)字。以上 TS 代碼經(jīng)過(guò)編譯后,會(huì)生成以下 ES5 代碼:

"use strict"; var inhabitantsOfMunich = 1464301; var distanceEarthSunInKm = 149600000; var fileSystemPermission = 504; var bytes = 262926349;

7.1 使用限制

雖然數(shù)字分隔符看起來(lái)很簡(jiǎn)單,但在使用時(shí)還是有一些限制。比如你只能在兩個(gè)數(shù)字之間添加 _ 分隔符。以下的使用方式是非法的:

// Numeric separators are not allowed here.(6188) 3_.141592 // Error 3._141592 // Error  // Numeric separators are not allowed here.(6188) 1_e10 // Error 1e_10 // Error  // Cannot find name '_126301'.(2304) _126301  // Error // Numeric separators are not allowed here.(6188) 126301_ // Error  // Cannot find name 'b111111000'.(2304) // An identifier or keyword cannot immediately follow a numeric literal.(1351) 0_b111111000 // Error  // Numeric separators are not allowed here.(6188) 0b_111111000 // Error

當(dāng)然你也不能連續(xù)使用多個(gè) _ 分隔符,比如:

// Multiple consecutive numeric separators are not permitted.(6189) 123__456 // Error

7.2 解析分隔符

此外,需要注意的是以下用于解析數(shù)字的函數(shù)是不支持分隔符:

  • Number()

  • parseInt()

  • parseFloat()

這里我們來(lái)看一下實(shí)際的例子:

Number('123_456') NaN parseInt('123_456') 123 parseFloat('123_456') 123

很明顯對(duì)于以上的結(jié)果不是我們所期望的,所以在處理分隔符時(shí)要特別注意。當(dāng)然要解決上述問(wèn)題,也很簡(jiǎn)單只需要非數(shù)字的字符刪掉即可。這里我們來(lái)定義一個(gè)  removeNonDigits 的函數(shù):

const RE_NON_DIGIT = /[^0-9]/gu;  function removeNonDigits(str) {   str = str.replace(RE_NON_DIGIT, '');   return Number(str); }

該函數(shù)通過(guò)調(diào)用字符串的 replace 方法來(lái)移除非數(shù)字的字符,具體的使用方式如下:

removeNonDigits('123_456') 123456 removeNonDigits('149,600,000') 149600000 removeNonDigits('1,407,836') 1407836

八、 語(yǔ)法

8.1 TypeScript 斷言

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

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

類型斷言有兩種形式:

8.1.1 “尖括號(hào)” 語(yǔ)法

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

8.1.2 as 語(yǔ)法

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

8.2 TypeScript 泛型

對(duì)于剛接觸 TypeScript 泛型的讀者來(lái)說(shuō),首次看到語(yǔ)法會(huì)感到陌生。其實(shí)它沒(méi)有什么特別,就像傳遞參數(shù)一樣,我們傳遞了我們想要用于特定函數(shù)調(diào)用的類型。

TypeScript中有哪些奇怪的符號(hào)

參考上面的圖片,當(dāng)我們調(diào)用 identity(1) ,Number 類型就像參數(shù) 1 一樣,它將在出現(xiàn) T 的任何位置填充該類型。圖中內(nèi)部的 T 被稱為類型變量,它是我們希望傳遞給 identity 函數(shù)的類型占位符,同時(shí)它被分配給 value 參數(shù)用來(lái)代替它的類型:此時(shí) T  充當(dāng)?shù)氖穷愋?,而不是特定?Number 類型。

其中 T 代表 Type,在定義泛型時(shí)通常用作第一個(gè)類型變量名稱。但實(shí)際上 T 可以用任何有效名稱代替。除了 T  之外,以下是常見(jiàn)泛型變量代表的意思:

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

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

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

其實(shí)并不是只能定義一個(gè)類型變量,我們可以引入希望定義的任何數(shù)量的類型變量。比如我們引入一個(gè)新的類型變量 U,用于擴(kuò)展我們定義的 identity  函數(shù):

function identity <T, U>(value: T, message: U) : T {   console.log(message);   return value; }  console.log(identity<Number, string>(68, "Semlinker"));

TypeScript中有哪些奇怪的符號(hào)

除了為類型變量顯式設(shè)定值之外,一種更常見(jiàn)的做法是使編譯器自動(dòng)選擇這些類型,從而使代碼更簡(jiǎn)潔。我們可以完全省略尖括號(hào),比如:

function identity <T, U>(value: T, message: U) : T {   console.log(message);   return value; }  console.log(identity(68, "Semlinker"));

對(duì)于上述代碼,編譯器足夠聰明,能夠知道我們的參數(shù)類型,并將它們賦值給 T 和 U,而不需要開(kāi)發(fā)人員顯式指定它們。

九、@XXX 裝飾器

9.1 裝飾器語(yǔ)法

對(duì)于一些剛接觸 TypeScript 的小伙伴來(lái)說(shuō),在第一次看到 @Plugin({...})  這種語(yǔ)法可能會(huì)覺(jué)得很驚訝。其實(shí)這是裝飾器的語(yǔ)法,裝飾器的本質(zhì)是一個(gè)函數(shù),通過(guò)裝飾器我們可以方便地定義與對(duì)象相關(guān)的元數(shù)據(jù)。

@Plugin({   pluginName: 'Device',   plugin: 'cordova-plugin-device',   pluginRef: 'device',   repo: 'https://github.com/apache/cordova-plugin-device',   platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'], }) @Injectable() export class Device extends IonicNativePlugin {}

在以上代碼中,我們通過(guò)裝飾器來(lái)保存 ionic-native 插件的相關(guān)元信息,而 @Plugin({...}) 中的 @  符號(hào)只是語(yǔ)法糖,為什么說(shuō)是語(yǔ)法糖呢?這里我們來(lái)看一下編譯生成的 ES5 代碼:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {     var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;     if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);     else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;     return c > 3 && r && Object.defineProperty(target, key, r), r; };  var Device = /** @class */ (function (_super) {     __extends(Device, _super);     function Device() {         return _super !== null && _super.apply(this, arguments) || this;     }     Device = __decorate([         Plugin({             pluginName: 'Device',             plugin: 'cordova-plugin-device',             pluginRef: 'device',             repo: 'https://github.com/apache/cordova-plugin-device',             platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],         }),         Injectable()     ], Device);     return Device; }(IonicNativePlugin));

通過(guò)生成的代碼可知,@Plugin({...}) 和 @Injectable()  最終會(huì)被轉(zhuǎn)換成普通的方法調(diào)用,它們的調(diào)用結(jié)果最終會(huì)以數(shù)組的形式作為參數(shù)傳遞給 __decorate 函數(shù),而在 __decorate 函數(shù)內(nèi)部會(huì)以 Device  類作為參數(shù)調(diào)用各自的類型裝飾器,從而擴(kuò)展對(duì)應(yīng)的功能。

9.2 裝飾器的分類

在 TypeScript 中裝飾器分為類裝飾器、屬性裝飾器、方法裝飾器和參數(shù)裝飾器四大類。

9.2.1 類裝飾器

類裝飾器聲明:

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

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

  • target: TFunction - 被裝飾的類

看完第一眼后,是不是感覺(jué)都不好了。沒(méi)事,我們馬上來(lái)個(gè)例子:

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

上面的例子中,我們定義了 Greeter 類裝飾器,同時(shí)我們使用了 @Greeter 語(yǔ)法糖,來(lái)使用裝飾器。

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

9.2.2 屬性裝飾器

屬性裝飾器聲明:

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

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

  • target: Object - 被裝飾的類

  • propertyKey: string | symbol - 被裝飾類的屬性名

趁熱打鐵,馬上來(lái)個(gè)例子熱熱身:

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";

以上代碼我們定義了一個(gè) logProperty 函數(shù),來(lái)跟蹤用戶對(duì)屬性的操作,當(dāng)代碼成功運(yùn)行后,在控制臺(tái)會(huì)輸出以下結(jié)果:

Set: name => semlinker Set: name => kakuqo

9.2.3 方法裝飾器

方法裝飾器聲明:

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

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

  • target: Object - 被裝飾的類

  • propertyKey: string | symbol - 方法名

  • descriptor: TypePropertyDescript - 屬性描述符

廢話不多說(shuō),直接上例子:

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);

9.2.4 參數(shù)裝飾器

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

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

參數(shù)裝飾器顧名思義,是用來(lái)裝飾函數(shù)參數(shù),它接收三個(gè)參數(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

十、#XXX 私有字段

在 TypeScript 3.8 版本就開(kāi)始支持 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ī)則:

  • 私有字段以 # 字符開(kāi)頭,有時(shí)我們稱之為私有名稱;

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

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

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

10.1 私有字段與 private 的區(qū)別

說(shuō)到這里使用 # 定義的私有字段與 private 修飾符定義字段有什么區(qū)別呢?現(xiàn)在我們先來(lái)看一個(gè) private 的示例:

class Person {   constructor(private name: string){} }  let person = new Person("Semlinker"); console.log(person.name);

在上面代碼中,我們創(chuàng)建了一個(gè) Person 類,該類中使用 private 修飾符定義了一個(gè)私有屬性 name,接著使用該類創(chuàng)建一個(gè) person  對(duì)象,然后通過(guò) person.name 來(lái)訪問(wèn)person 對(duì)象的私有屬性,這時(shí) TypeScript 編譯器會(huì)提示以下異常:

Property 'name' is private and only accessible within class 'Person'.(2341)

那如何解決這個(gè)異常呢?當(dāng)然你可以使用類型斷言把 person 轉(zhuǎn)為 any 類型:

console.log((person as any).name);

通過(guò)這種方式雖然解決了 TypeScript 編譯器的異常提示,但是在運(yùn)行時(shí)我們還是可以訪問(wèn)到 Person  類內(nèi)部的私有屬性,為什么會(huì)這樣呢?我們來(lái)看一下編譯生成的 ES5 代碼,也許你就知道答案了:

var Person = /** @class */ (function () {     function Person(name) {       this.name = name;     }     return Person; }());  var person = new Person("Semlinker"); console.log(person.name);

這時(shí)相信有些小伙伴會(huì)好奇,在 TypeScript 3.8 以上版本通過(guò) # 號(hào)定義的私有字段編譯后會(huì)生成什么代碼:

class Person {   #name: string;    constructor(name: string) {     this.#name = name;   }    greet() {     console.log(`Hello, my name is ${this.#name}!`);   } }

以上代碼目標(biāo)設(shè)置為 ES2015,會(huì)編譯生成以下代碼:

"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet)    || function (receiver, privateMap, value) {     if (!privateMap.has(receiver)) {       throw new TypeError("attempted to set private field on non-instance");     }     privateMap.set(receiver, value);     return value; };  var __classPrivateFieldGet = (this && this.__classPrivateFieldGet)    || function (receiver, privateMap) {     if (!privateMap.has(receiver)) {       throw new TypeError("attempted to get private field on non-instance");     }     return privateMap.get(receiver); };  var _name; class Person {     constructor(name) {       _name.set(this, void 0);       __classPrivateFieldSet(this, _name, name);     }     greet() {       console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);     } } _name = new WeakMap();

通過(guò)觀察上述代碼,使用 # 號(hào)定義的 ECMAScript 私有字段,會(huì)通過(guò) WeakMap 對(duì)象來(lái)存儲(chǔ),同時(shí)編譯器會(huì)生成  __classPrivateFieldSet 和 __classPrivateFieldGet 這兩個(gè)方法用于設(shè)置值和獲取值。

感謝各位的閱讀,以上就是“TypeScript中有哪些奇怪的符號(hào)”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)TypeScript中有哪些奇怪的符號(hào)這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI